summaryrefslogtreecommitdiffstats
path: root/app/vectors
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/vectors
parentInitial commit. (diff)
downloadgimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz
gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/vectors')
-rw-r--r--app/vectors/Makefile.am44
-rw-r--r--app/vectors/Makefile.in992
-rw-r--r--app/vectors/gimpanchor.c71
-rw-r--r--app/vectors/gimpanchor.h48
-rw-r--r--app/vectors/gimpbezierstroke.c2309
-rw-r--r--app/vectors/gimpbezierstroke.h84
-rw-r--r--app/vectors/gimpstroke-new.c47
-rw-r--r--app/vectors/gimpstroke-new.h31
-rw-r--r--app/vectors/gimpstroke.c1431
-rw-r--r--app/vectors/gimpstroke.h343
-rw-r--r--app/vectors/gimpvectors-compat.c285
-rw-r--r--app/vectors/gimpvectors-compat.h48
-rw-r--r--app/vectors/gimpvectors-export.c333
-rw-r--r--app/vectors/gimpvectors-export.h30
-rw-r--r--app/vectors/gimpvectors-import.c1784
-rw-r--r--app/vectors/gimpvectors-import.h44
-rw-r--r--app/vectors/gimpvectors-preview.c93
-rw-r--r--app/vectors/gimpvectors-preview.h32
-rw-r--r--app/vectors/gimpvectors-warp.c210
-rw-r--r--app/vectors/gimpvectors-warp.h36
-rw-r--r--app/vectors/gimpvectors.c1255
-rw-r--r--app/vectors/gimpvectors.h184
-rw-r--r--app/vectors/gimpvectorsmodundo.c141
-rw-r--r--app/vectors/gimpvectorsmodundo.h52
-rw-r--r--app/vectors/gimpvectorspropundo.c94
-rw-r--r--app/vectors/gimpvectorspropundo.h50
-rw-r--r--app/vectors/gimpvectorsundo.c213
-rw-r--r--app/vectors/gimpvectorsundo.h54
-rw-r--r--app/vectors/vectors-enums.h46
-rw-r--r--app/vectors/vectors-types.h37
30 files changed, 10421 insertions, 0 deletions
diff --git a/app/vectors/Makefile.am b/app/vectors/Makefile.am
new file mode 100644
index 0000000..aa6e10d
--- /dev/null
+++ b/app/vectors/Makefile.am
@@ -0,0 +1,44 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Vectors\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libappvectors.a
+
+libappvectors_a_SOURCES = \
+ vectors-enums.h \
+ vectors-types.h \
+ gimpanchor.c \
+ gimpanchor.h \
+ gimpbezierstroke.h \
+ gimpbezierstroke.c \
+ gimpstroke.h \
+ gimpstroke.c \
+ gimpstroke-new.h \
+ gimpstroke-new.c \
+ gimpvectors.c \
+ gimpvectors.h \
+ gimpvectors-compat.c \
+ gimpvectors-compat.h \
+ gimpvectors-export.c \
+ gimpvectors-export.h \
+ gimpvectors-import.c \
+ gimpvectors-import.h \
+ gimpvectors-preview.c \
+ gimpvectors-preview.h \
+ gimpvectors-warp.c \
+ gimpvectors-warp.h \
+ gimpvectorsmodundo.c \
+ gimpvectorsmodundo.h \
+ gimpvectorspropundo.c \
+ gimpvectorspropundo.h \
+ gimpvectorsundo.c \
+ gimpvectorsundo.h
diff --git a/app/vectors/Makefile.in b/app/vectors/Makefile.in
new file mode 100644
index 0000000..5a08c69
--- /dev/null
+++ b/app/vectors/Makefile.in
@@ -0,0 +1,992 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = app/vectors
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4macros/alsa.m4 \
+ $(top_srcdir)/m4macros/ax_compare_version.m4 \
+ $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \
+ $(top_srcdir)/m4macros/detectcflags.m4 \
+ $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libappvectors_a_AR = $(AR) $(ARFLAGS)
+libappvectors_a_LIBADD =
+am_libappvectors_a_OBJECTS = gimpanchor.$(OBJEXT) \
+ gimpbezierstroke.$(OBJEXT) gimpstroke.$(OBJEXT) \
+ gimpstroke-new.$(OBJEXT) gimpvectors.$(OBJEXT) \
+ gimpvectors-compat.$(OBJEXT) gimpvectors-export.$(OBJEXT) \
+ gimpvectors-import.$(OBJEXT) gimpvectors-preview.$(OBJEXT) \
+ gimpvectors-warp.$(OBJEXT) gimpvectorsmodundo.$(OBJEXT) \
+ gimpvectorspropundo.$(OBJEXT) gimpvectorsundo.$(OBJEXT)
+libappvectors_a_OBJECTS = $(am_libappvectors_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/gimpanchor.Po \
+ ./$(DEPDIR)/gimpbezierstroke.Po ./$(DEPDIR)/gimpstroke-new.Po \
+ ./$(DEPDIR)/gimpstroke.Po ./$(DEPDIR)/gimpvectors-compat.Po \
+ ./$(DEPDIR)/gimpvectors-export.Po \
+ ./$(DEPDIR)/gimpvectors-import.Po \
+ ./$(DEPDIR)/gimpvectors-preview.Po \
+ ./$(DEPDIR)/gimpvectors-warp.Po ./$(DEPDIR)/gimpvectors.Po \
+ ./$(DEPDIR)/gimpvectorsmodundo.Po \
+ ./$(DEPDIR)/gimpvectorspropundo.Po \
+ ./$(DEPDIR)/gimpvectorsundo.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libappvectors_a_SOURCES)
+DIST_SOURCES = $(libappvectors_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+AA_LIBS = @AA_LIBS@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALL_LINGUAS = @ALL_LINGUAS@
+ALSA_CFLAGS = @ALSA_CFLAGS@
+ALSA_LIBS = @ALSA_LIBS@
+ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPSTREAM_UTIL = @APPSTREAM_UTIL@
+AR = @AR@
+AS = @AS@
+ATK_CFLAGS = @ATK_CFLAGS@
+ATK_LIBS = @ATK_LIBS@
+ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BABL_CFLAGS = @BABL_CFLAGS@
+BABL_LIBS = @BABL_LIBS@
+BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@
+BUG_REPORT_URL = @BUG_REPORT_URL@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+BZIP2_LIBS = @BZIP2_LIBS@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@
+CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@
+CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@
+CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCAS = @CCAS@
+CCASDEPMODE = @CCASDEPMODE@
+CCASFLAGS = @CCASFLAGS@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CC_VERSION = @CC_VERSION@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DESKTOP_DATADIR = @DESKTOP_DATADIR@
+DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@
+DLLTOOL = @DLLTOOL@
+DOC_SHOOTER = @DOC_SHOOTER@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILE_AA = @FILE_AA@
+FILE_EXR = @FILE_EXR@
+FILE_HEIF = @FILE_HEIF@
+FILE_JP2_LOAD = @FILE_JP2_LOAD@
+FILE_JPEGXL = @FILE_JPEGXL@
+FILE_MNG = @FILE_MNG@
+FILE_PDF_SAVE = @FILE_PDF_SAVE@
+FILE_PS = @FILE_PS@
+FILE_WMF = @FILE_WMF@
+FILE_XMC = @FILE_XMC@
+FILE_XPM = @FILE_XPM@
+FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@
+FONTCONFIG_LIBS = @FONTCONFIG_LIBS@
+FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@
+FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GDBUS_CODEGEN = @GDBUS_CODEGEN@
+GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@
+GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@
+GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@
+GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@
+GEGL = @GEGL@
+GEGL_CFLAGS = @GEGL_CFLAGS@
+GEGL_LIBS = @GEGL_LIBS@
+GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@
+GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GEXIV2_CFLAGS = @GEXIV2_CFLAGS@
+GEXIV2_LIBS = @GEXIV2_LIBS@
+GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@
+GIMP_API_VERSION = @GIMP_API_VERSION@
+GIMP_APP_VERSION = @GIMP_APP_VERSION@
+GIMP_BINARY_AGE = @GIMP_BINARY_AGE@
+GIMP_COMMAND = @GIMP_COMMAND@
+GIMP_DATA_VERSION = @GIMP_DATA_VERSION@
+GIMP_FULL_NAME = @GIMP_FULL_NAME@
+GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@
+GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@
+GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@
+GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@
+GIMP_MKENUMS = @GIMP_MKENUMS@
+GIMP_MODULES = @GIMP_MODULES@
+GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@
+GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@
+GIMP_PLUGINS = @GIMP_PLUGINS@
+GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@
+GIMP_REAL_VERSION = @GIMP_REAL_VERSION@
+GIMP_RELEASE = @GIMP_RELEASE@
+GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@
+GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@
+GIMP_UNSTABLE = @GIMP_UNSTABLE@
+GIMP_USER_VERSION = @GIMP_USER_VERSION@
+GIMP_VERSION = @GIMP_VERSION@
+GIO_CFLAGS = @GIO_CFLAGS@
+GIO_LIBS = @GIO_LIBS@
+GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@
+GIO_UNIX_LIBS = @GIO_UNIX_LIBS@
+GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@
+GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@
+GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@
+GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+GS_LIBS = @GS_LIBS@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
+GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
+GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
+GTKDOC_MKPDF = @GTKDOC_MKPDF@
+GTKDOC_REBASE = @GTKDOC_REBASE@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@
+GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@
+GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@
+GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@
+GUDEV_CFLAGS = @GUDEV_CFLAGS@
+GUDEV_LIBS = @GUDEV_LIBS@
+HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@
+HARFBUZZ_LIBS = @HARFBUZZ_LIBS@
+HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@
+HAVE_CXX14 = @HAVE_CXX14@
+HAVE_FINITE = @HAVE_FINITE@
+HAVE_ISFINITE = @HAVE_ISFINITE@
+HAVE_VFORK = @HAVE_VFORK@
+HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@
+INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@
+INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@
+INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@
+ISO_CODES_LOCATION = @ISO_CODES_LOCATION@
+JPEG_LIBS = @JPEG_LIBS@
+JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@
+JSON_GLIB_LIBS = @JSON_GLIB_LIBS@
+JXL_CFLAGS = @JXL_CFLAGS@
+JXL_LIBS = @JXL_LIBS@
+JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@
+JXL_THREADS_LIBS = @JXL_THREADS_LIBS@
+LCMS_CFLAGS = @LCMS_CFLAGS@
+LCMS_LIBS = @LCMS_LIBS@
+LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@
+LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@
+LIBHEIF_LIBS = @LIBHEIF_LIBS@
+LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@
+LIBJXL_REQUIRED_VERSION = @LIBJXL_REQUIRED_VERSION@
+LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@
+LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@
+LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@
+LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LT_VERSION_INFO = @LT_VERSION_INFO@
+LZMA_CFLAGS = @LZMA_CFLAGS@
+LZMA_LIBS = @LZMA_LIBS@
+MAIL = @MAIL@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@
+MIME_INFO_LIBS = @MIME_INFO_LIBS@
+MIME_TYPES = @MIME_TYPES@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@
+MNG_CFLAGS = @MNG_CFLAGS@
+MNG_LIBS = @MNG_LIBS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@
+MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@
+NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@
+NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENEXR_CFLAGS = @OPENEXR_CFLAGS@
+OPENEXR_LIBS = @OPENEXR_LIBS@
+OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@
+OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@
+OPENJPEG_LIBS = @OPENJPEG_LIBS@
+OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@
+PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@
+PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@
+PATHSEP = @PATHSEP@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@
+PERL_VERSION = @PERL_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PNG_CFLAGS = @PNG_CFLAGS@
+PNG_LIBS = @PNG_LIBS@
+POFILES = @POFILES@
+POPPLER_CFLAGS = @POPPLER_CFLAGS@
+POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@
+POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@
+POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@
+POPPLER_LIBS = @POPPLER_LIBS@
+POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYBIN_PATH = @PYBIN_PATH@
+PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@
+PYCAIRO_LIBS = @PYCAIRO_LIBS@
+PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@
+PYGTK_CFLAGS = @PYGTK_CFLAGS@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYGTK_LIBS = @PYGTK_LIBS@
+PYLINK_LIBS = @PYLINK_LIBS@
+PYTHON = @PYTHON@
+PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@
+RT_LIBS = @RT_LIBS@
+SCREENSHOT_LIBS = @SCREENSHOT_LIBS@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@
+SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@
+SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@
+STRIP = @STRIP@
+SVG_CFLAGS = @SVG_CFLAGS@
+SVG_LIBS = @SVG_LIBS@
+SYMPREFIX = @SYMPREFIX@
+TIFF_LIBS = @TIFF_LIBS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
+WEBKIT_LIBS = @WEBKIT_LIBS@
+WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@
+WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@
+WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@
+WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@
+WEBPMUX_LIBS = @WEBPMUX_LIBS@
+WEBP_CFLAGS = @WEBP_CFLAGS@
+WEBP_LIBS = @WEBP_LIBS@
+WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@
+WEB_PAGE = @WEB_PAGE@
+WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@
+WINDRES = @WINDRES@
+WMF_CFLAGS = @WMF_CFLAGS@
+WMF_CONFIG = @WMF_CONFIG@
+WMF_LIBS = @WMF_LIBS@
+WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@
+XDG_EMAIL = @XDG_EMAIL@
+XFIXES_CFLAGS = @XFIXES_CFLAGS@
+XFIXES_LIBS = @XFIXES_LIBS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@
+XMC_CFLAGS = @XMC_CFLAGS@
+XMC_LIBS = @XMC_LIBS@
+XMKMF = @XMKMF@
+XMLLINT = @XMLLINT@
+XMU_LIBS = @XMU_LIBS@
+XPM_LIBS = @XPM_LIBS@
+XSLTPROC = @XSLTPROC@
+XVFB_RUN = @XVFB_RUN@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+Z_LIBS = @Z_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gimpdatadir = @gimpdatadir@
+gimpdir = @gimpdir@
+gimplocaledir = @gimplocaledir@
+gimpplugindir = @gimpplugindir@
+gimpsysconfdir = @gimpsysconfdir@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+intltool__v_merge_options_ = @intltool__v_merge_options_@
+intltool__v_merge_options_0 = @intltool__v_merge_options_0@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+manpage_gimpdir = @manpage_gimpdir@
+mkdir_p = @mkdir_p@
+ms_librarian = @ms_librarian@
+mypaint_brushes_dir = @mypaint_brushes_dir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Vectors\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libappvectors.a
+libappvectors_a_SOURCES = \
+ vectors-enums.h \
+ vectors-types.h \
+ gimpanchor.c \
+ gimpanchor.h \
+ gimpbezierstroke.h \
+ gimpbezierstroke.c \
+ gimpstroke.h \
+ gimpstroke.c \
+ gimpstroke-new.h \
+ gimpstroke-new.c \
+ gimpvectors.c \
+ gimpvectors.h \
+ gimpvectors-compat.c \
+ gimpvectors-compat.h \
+ gimpvectors-export.c \
+ gimpvectors-export.h \
+ gimpvectors-import.c \
+ gimpvectors-import.h \
+ gimpvectors-preview.c \
+ gimpvectors-preview.h \
+ gimpvectors-warp.c \
+ gimpvectors-warp.h \
+ gimpvectorsmodundo.c \
+ gimpvectorsmodundo.h \
+ gimpvectorspropundo.c \
+ gimpvectorspropundo.h \
+ gimpvectorsundo.c \
+ gimpvectorsundo.h
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/vectors/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/vectors/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libappvectors.a: $(libappvectors_a_OBJECTS) $(libappvectors_a_DEPENDENCIES) $(EXTRA_libappvectors_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libappvectors.a
+ $(AM_V_AR)$(libappvectors_a_AR) libappvectors.a $(libappvectors_a_OBJECTS) $(libappvectors_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libappvectors.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpanchor.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbezierstroke.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstroke-new.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstroke.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-compat.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-export.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-import.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-preview.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-warp.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorsmodundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorspropundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorsundo.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/gimpanchor.Po
+ -rm -f ./$(DEPDIR)/gimpbezierstroke.Po
+ -rm -f ./$(DEPDIR)/gimpstroke-new.Po
+ -rm -f ./$(DEPDIR)/gimpstroke.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-compat.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-export.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-import.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-preview.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-warp.Po
+ -rm -f ./$(DEPDIR)/gimpvectors.Po
+ -rm -f ./$(DEPDIR)/gimpvectorsmodundo.Po
+ -rm -f ./$(DEPDIR)/gimpvectorspropundo.Po
+ -rm -f ./$(DEPDIR)/gimpvectorsundo.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/gimpanchor.Po
+ -rm -f ./$(DEPDIR)/gimpbezierstroke.Po
+ -rm -f ./$(DEPDIR)/gimpstroke-new.Po
+ -rm -f ./$(DEPDIR)/gimpstroke.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-compat.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-export.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-import.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-preview.Po
+ -rm -f ./$(DEPDIR)/gimpvectors-warp.Po
+ -rm -f ./$(DEPDIR)/gimpvectors.Po
+ -rm -f ./$(DEPDIR)/gimpvectorsmodundo.Po
+ -rm -f ./$(DEPDIR)/gimpvectorspropundo.Po
+ -rm -f ./$(DEPDIR)/gimpvectorsundo.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/app/vectors/gimpanchor.c b/app/vectors/gimpanchor.c
new file mode 100644
index 0000000..f5085cd
--- /dev/null
+++ b/app/vectors/gimpanchor.c
@@ -0,0 +1,71 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpanchor.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "vectors-types.h"
+
+#include "gimpanchor.h"
+
+
+GType
+gimp_anchor_get_type (void)
+{
+ static GType anchor_type = 0;
+
+ if (!anchor_type)
+ anchor_type = g_boxed_type_register_static ("GimpAnchor",
+ (GBoxedCopyFunc) gimp_anchor_copy,
+ (GBoxedFreeFunc) gimp_anchor_free);
+
+ return anchor_type;
+}
+
+GimpAnchor *
+gimp_anchor_new (GimpAnchorType type,
+ const GimpCoords *position)
+{
+ GimpAnchor *anchor = g_slice_new0 (GimpAnchor);
+
+ anchor->type = type;
+
+ if (position)
+ anchor->position = *position;
+
+ return anchor;
+}
+
+GimpAnchor *
+gimp_anchor_copy (const GimpAnchor *anchor)
+{
+ g_return_val_if_fail (anchor != NULL, NULL);
+
+ return g_slice_dup (GimpAnchor, anchor);
+}
+
+void
+gimp_anchor_free (GimpAnchor *anchor)
+{
+ g_return_if_fail (anchor != NULL);
+
+ g_slice_free (GimpAnchor, anchor);
+}
diff --git a/app/vectors/gimpanchor.h b/app/vectors/gimpanchor.h
new file mode 100644
index 0000000..75203a4
--- /dev/null
+++ b/app/vectors/gimpanchor.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpanchor.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ANCHOR_H__
+#define __GIMP_ANCHOR_H__
+
+#define GIMP_ANCHOR(anchor) ((GimpAnchor *) (anchor))
+
+#define GIMP_TYPE_ANCHOR (gimp_anchor_get_type ())
+#define GIMP_VALUE_HOLDS_ANCHOR(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_ANCHOR))
+
+GType gimp_anchor_get_type (void) G_GNUC_CONST;
+
+
+struct _GimpAnchor
+{
+ GimpCoords position;
+
+ GimpAnchorType type; /* Interpretation dependent on GimpStroke type */
+ gboolean selected;
+};
+
+
+GimpAnchor * gimp_anchor_new (GimpAnchorType type,
+ const GimpCoords *position);
+
+GimpAnchor * gimp_anchor_copy (const GimpAnchor *anchor);
+void gimp_anchor_free (GimpAnchor *anchor);
+
+
+#endif /* __GIMP_ANCHOR_H__ */
diff --git a/app/vectors/gimpbezierstroke.c b/app/vectors/gimpbezierstroke.c
new file mode 100644
index 0000000..3cfe735
--- /dev/null
+++ b/app/vectors/gimpbezierstroke.c
@@ -0,0 +1,2309 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbezierstroke.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <cairo.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpbezierdesc.h"
+#include "core/gimpcoords.h"
+#include "core/gimpcoords-interpolate.h"
+
+#include "gimpanchor.h"
+#include "gimpbezierstroke.h"
+
+
+/* local prototypes */
+
+static gdouble
+ gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+static gdouble
+ gimp_bezier_stroke_segment_nearest_point_get
+ (const GimpCoords *beziercoords,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos,
+ gint depth);
+static gdouble
+ gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+static gdouble
+ gimp_bezier_stroke_segment_nearest_tangent_get
+ (const GimpCoords *beziercoords,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos);
+static void
+ gimp_bezier_stroke_anchor_move_relative
+ (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_move_absolute
+ (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor);
+static gboolean
+ gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static void
+ gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+static void gimp_bezier_stroke_close (GimpStroke *stroke);
+
+static GimpStroke *
+ gimp_bezier_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+static gboolean
+ gimp_bezier_stroke_anchor_is_insertable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static GimpAnchor *
+ gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static gboolean
+ gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+static gboolean
+ gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+static GArray *
+ gimp_bezier_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *closed);
+static GimpBezierDesc *
+ gimp_bezier_stroke_make_bezier (GimpStroke *stroke);
+static void gimp_bezier_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+static void gimp_bezier_stroke_finalize (GObject *object);
+
+
+static GList * gimp_bezier_stroke_get_anchor_listitem
+ (GList *list);
+
+
+G_DEFINE_TYPE (GimpBezierStroke, gimp_bezier_stroke, GIMP_TYPE_STROKE)
+
+#define parent_class gimp_bezier_stroke_parent_class
+
+
+static void
+gimp_bezier_stroke_class_init (GimpBezierStrokeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpStrokeClass *stroke_class = GIMP_STROKE_CLASS (klass);
+
+ object_class->finalize = gimp_bezier_stroke_finalize;
+
+ stroke_class->nearest_point_get = gimp_bezier_stroke_nearest_point_get;
+ stroke_class->nearest_tangent_get = gimp_bezier_stroke_nearest_tangent_get;
+ stroke_class->nearest_intersection_get = NULL;
+ stroke_class->anchor_move_relative = gimp_bezier_stroke_anchor_move_relative;
+ stroke_class->anchor_move_absolute = gimp_bezier_stroke_anchor_move_absolute;
+ stroke_class->anchor_convert = gimp_bezier_stroke_anchor_convert;
+ stroke_class->anchor_delete = gimp_bezier_stroke_anchor_delete;
+ stroke_class->point_is_movable = gimp_bezier_stroke_point_is_movable;
+ stroke_class->point_move_relative = gimp_bezier_stroke_point_move_relative;
+ stroke_class->point_move_absolute = gimp_bezier_stroke_point_move_absolute;
+ stroke_class->close = gimp_bezier_stroke_close;
+ stroke_class->open = gimp_bezier_stroke_open;
+ stroke_class->anchor_is_insertable = gimp_bezier_stroke_anchor_is_insertable;
+ stroke_class->anchor_insert = gimp_bezier_stroke_anchor_insert;
+ stroke_class->is_extendable = gimp_bezier_stroke_is_extendable;
+ stroke_class->extend = gimp_bezier_stroke_extend;
+ stroke_class->connect_stroke = gimp_bezier_stroke_connect_stroke;
+ stroke_class->interpolate = gimp_bezier_stroke_interpolate;
+ stroke_class->make_bezier = gimp_bezier_stroke_make_bezier;
+ stroke_class->transform = gimp_bezier_stroke_transform;
+}
+
+static void
+gimp_bezier_stroke_init (GimpBezierStroke *stroke)
+{
+}
+
+static void
+gimp_bezier_stroke_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+/* Bezier specific functions */
+
+GimpStroke *
+gimp_bezier_stroke_new (void)
+{
+ return g_object_new (GIMP_TYPE_BEZIER_STROKE, NULL);
+}
+
+
+GimpStroke *
+gimp_bezier_stroke_new_from_coords (const GimpCoords *coords,
+ gint n_coords,
+ gboolean closed)
+{
+ GimpStroke *stroke;
+ GimpAnchor *last_anchor;
+ gint count;
+
+ g_return_val_if_fail (coords != NULL, NULL);
+ g_return_val_if_fail (n_coords >= 3, NULL);
+ g_return_val_if_fail ((n_coords % 3) == 0, NULL);
+
+ stroke = gimp_bezier_stroke_new ();
+
+ last_anchor = NULL;
+
+ for (count = 0; count < n_coords; count++)
+ last_anchor = gimp_bezier_stroke_extend (stroke,
+ &coords[count],
+ last_anchor,
+ EXTEND_SIMPLE);
+
+ if (closed)
+ gimp_stroke_close (stroke);
+
+ return stroke;
+}
+
+static void
+gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ GList *list;
+ GList *list2;
+ gint i;
+
+ /* Anchors always are surrounded by two handles that have to
+ * be deleted too
+ */
+
+ list2 = g_queue_find (stroke->anchors, anchor);
+ list = g_list_previous (list2);
+
+ for (i = 0; i < 3; i++)
+ {
+ g_return_if_fail (list != NULL);
+
+ list2 = g_list_next (list);
+ gimp_anchor_free (list->data);
+ g_queue_delete_link (stroke->anchors, list);
+ list = list2;
+ }
+}
+
+static GimpStroke *
+gimp_bezier_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ GList *list;
+ GList *list2;
+ GimpStroke *new_stroke = NULL;
+
+ list = g_queue_find (stroke->anchors, end_anchor);
+
+ g_return_val_if_fail (list != NULL && list->next != NULL, NULL);
+
+ list = g_list_next (list); /* protect the handle... */
+
+ list2 = list->next;
+ list->next = NULL;
+
+ if (list2 != NULL)
+ {
+ GList *tail = stroke->anchors->tail;
+
+ stroke->anchors->tail = list;
+ stroke->anchors->length -= g_list_length (list2);
+
+ list2->prev = NULL;
+
+ if (stroke->closed)
+ {
+ GList *l;
+
+ for (l = tail; l; l = g_list_previous (l))
+ g_queue_push_head (stroke->anchors, l->data);
+
+ g_list_free (list2);
+ }
+ else
+ {
+ new_stroke = gimp_bezier_stroke_new ();
+ new_stroke->anchors->head = list2;
+ new_stroke->anchors->tail = g_list_last (list2);
+ new_stroke->anchors->length = g_list_length (list2);
+ }
+ }
+
+ stroke->closed = FALSE;
+ g_object_notify (G_OBJECT (stroke), "closed");
+
+ return new_stroke;
+}
+
+static gboolean
+gimp_bezier_stroke_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return (g_queue_find (stroke->anchors, predec) != NULL);
+}
+
+
+static GimpAnchor *
+gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ GList *segment_start;
+ GList *list;
+ GList *list2;
+ GimpCoords subdivided[8];
+ GimpCoords beziercoords[4];
+ gint i;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ if (! segment_start)
+ return NULL;
+
+ list = segment_start;
+
+ for (i = 0; i <= 3; i++)
+ {
+ beziercoords[i] = GIMP_ANCHOR (list->data)->position;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ subdivided[0] = beziercoords[0];
+ subdivided[6] = beziercoords[3];
+
+ gimp_coords_mix (1-position, &(beziercoords[0]),
+ position, &(beziercoords[1]),
+ &(subdivided[1]));
+
+ gimp_coords_mix (1-position, &(beziercoords[1]),
+ position, &(beziercoords[2]),
+ &(subdivided[7]));
+
+ gimp_coords_mix (1-position, &(beziercoords[2]),
+ position, &(beziercoords[3]),
+ &(subdivided[5]));
+
+ gimp_coords_mix (1-position, &(subdivided[1]),
+ position, &(subdivided[7]),
+ &(subdivided[2]));
+
+ gimp_coords_mix (1-position, &(subdivided[7]),
+ position, &(subdivided[5]),
+ &(subdivided[4]));
+
+ gimp_coords_mix (1-position, &(subdivided[2]),
+ position, &(subdivided[4]),
+ &(subdivided[3]));
+
+ /* subdivided 0-6 contains the bezier segment subdivided at <position> */
+
+ list = segment_start;
+
+ for (i = 0; i <= 6; i++)
+ {
+ if (i >= 2 && i <= 4)
+ {
+ list2 = g_list_append (NULL,
+ gimp_anchor_new ((i == 3 ?
+ GIMP_ANCHOR_ANCHOR:
+ GIMP_ANCHOR_CONTROL),
+ &(subdivided[i])));
+ /* insert it *before* list manually. */
+ list2->next = list;
+ list2->prev = list->prev;
+ if (list->prev)
+ list->prev->next = list2;
+ list->prev = list2;
+
+ list = list2;
+
+ if (i == 3)
+ segment_start = list;
+ }
+ else
+ {
+ GIMP_ANCHOR (list->data)->position = subdivided[i];
+ }
+
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ stroke->anchors->head = g_list_first (list);
+ stroke->anchors->tail = g_list_last (list);
+ stroke->anchors->length += 3;
+
+ return GIMP_ANCHOR (segment_start->data);
+}
+
+
+static gboolean
+gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return (g_queue_find (stroke->anchors, predec) != NULL);
+}
+
+
+static void
+gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords offsetcoords[2];
+ GList *segment_start;
+ GList *list;
+ gint i;
+ gdouble feel_good;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ g_return_if_fail (segment_start != NULL);
+
+ /* dragging close to endpoints just moves the handle related to
+ * the endpoint. Just make sure that feel_good is in the range from
+ * 0 to 1. The 1.0 / 6.0 and 5.0 / 6.0 are duplicated in
+ * tools/gimpvectortool.c.
+ */
+ if (position <= 1.0 / 6.0)
+ feel_good = 0;
+ else if (position <= 0.5)
+ feel_good = (pow((6 * position - 1) / 2.0, 3)) / 2;
+ else if (position <= 5.0 / 6.0)
+ feel_good = (1 - pow((6 * (1-position) - 1) / 2.0, 3)) / 2 + 0.5;
+ else
+ feel_good = 1;
+
+ gimp_coords_scale ((1-feel_good)/(3*position*
+ (1-position)*(1-position)),
+ deltacoord,
+ &(offsetcoords[0]));
+ gimp_coords_scale (feel_good/(3*position*position*(1-position)),
+ deltacoord,
+ &(offsetcoords[1]));
+
+ list = segment_start;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+
+ for (i = 0; i <= 1; i++)
+ {
+ gimp_stroke_anchor_move_relative (stroke, GIMP_ANCHOR (list->data),
+ &(offsetcoords[i]), feature);
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+}
+
+
+static void
+gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords deltacoord;
+ GimpCoords tmp1, tmp2, abs_pos;
+ GimpCoords beziercoords[4];
+ GList *segment_start;
+ GList *list;
+ gint i;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ g_return_if_fail (segment_start != NULL);
+
+ list = segment_start;
+
+ for (i = 0; i <= 3; i++)
+ {
+ beziercoords[i] = GIMP_ANCHOR (list->data)->position;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ gimp_coords_mix ((1-position)*(1-position)*(1-position), &(beziercoords[0]),
+ 3*(1-position)*(1-position)*position, &(beziercoords[1]),
+ &tmp1);
+ gimp_coords_mix (3*(1-position)*position*position, &(beziercoords[2]),
+ position*position*position, &(beziercoords[3]),
+ &tmp2);
+ gimp_coords_add (&tmp1, &tmp2, &abs_pos);
+
+ gimp_coords_difference (coord, &abs_pos, &deltacoord);
+
+ gimp_bezier_stroke_point_move_relative (stroke, predec, position,
+ &deltacoord, feature);
+}
+
+static void
+gimp_bezier_stroke_close (GimpStroke *stroke)
+{
+ GList *start;
+ GList *end;
+ GimpAnchor *anchor;
+
+ start = stroke->anchors->head;
+ end = stroke->anchors->tail;
+
+ g_return_if_fail (start->next != NULL && end->prev != NULL);
+
+ if (start->next != end->prev)
+ {
+ if (gimp_coords_equal (&(GIMP_ANCHOR (start->next->data)->position),
+ &(GIMP_ANCHOR (start->data)->position)) &&
+ gimp_coords_equal (&(GIMP_ANCHOR (start->data)->position),
+ &(GIMP_ANCHOR (end->data)->position)) &&
+ gimp_coords_equal (&(GIMP_ANCHOR (end->data)->position),
+ &(GIMP_ANCHOR (end->prev->data)->position)))
+ {
+ /* redundant segment */
+
+ gimp_anchor_free (stroke->anchors->tail->data);
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ gimp_anchor_free (stroke->anchors->tail->data);
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ anchor = stroke->anchors->tail->data;
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ gimp_anchor_free (stroke->anchors->head->data);
+ stroke->anchors->head->data = anchor;
+ }
+ }
+
+ GIMP_STROKE_CLASS (parent_class)->close (stroke);
+}
+
+static gdouble
+gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ gdouble min_dist, dist, pos;
+ GimpCoords point = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
+ GimpCoords segmentcoords[4];
+ GList *anchorlist;
+ GimpAnchor *segment_start;
+ GimpAnchor *segment_end = NULL;
+ GimpAnchor *anchor;
+ gint count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1.0;
+
+ count = 0;
+ min_dist = -1;
+ pos = 0;
+
+ for (anchorlist = stroke->anchors->head;
+ GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ segment_start = anchorlist->data;
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ segment_end = anchorlist->data;
+ dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords,
+ coord, precision,
+ &point, &pos,
+ 10);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (ret_point)
+ *ret_point = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+
+ segment_start = anchorlist->data;
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ if (stroke->closed && stroke->anchors->head)
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+
+ anchorlist = g_list_next (anchorlist);
+
+ if (anchorlist)
+ {
+ segment_end = GIMP_ANCHOR (anchorlist->data);
+ segmentcoords[3] = segment_end->position;
+ }
+
+ dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords,
+ coord, precision,
+ &point, &pos,
+ 10);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (ret_point)
+ *ret_point = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+ }
+
+ return min_dist;
+}
+
+
+static gdouble
+gimp_bezier_stroke_segment_nearest_point_get (const GimpCoords *beziercoords,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos,
+ gint depth)
+{
+ /*
+ * beziercoords has to contain four GimpCoords with the four control points
+ * of the bezier segment. We subdivide it at the parameter 0.5.
+ */
+
+ GimpCoords subdivided[8];
+ gdouble dist1, dist2;
+ GimpCoords point1, point2;
+ gdouble pos1, pos2;
+
+ gimp_coords_difference (&beziercoords[1], &beziercoords[0], &point1);
+ gimp_coords_difference (&beziercoords[3], &beziercoords[2], &point2);
+
+ if (! depth || (gimp_coords_bezier_is_straight (beziercoords, precision) &&
+ gimp_coords_length_squared (&point1) < precision &&
+ gimp_coords_length_squared (&point2) < precision))
+ {
+ GimpCoords line, dcoord;
+ gdouble length2, scalar;
+ gint i;
+
+ gimp_coords_difference (&(beziercoords[3]),
+ &(beziercoords[0]),
+ &line);
+
+ gimp_coords_difference (coord,
+ &(beziercoords[0]),
+ &dcoord);
+
+ length2 = gimp_coords_scalarprod (&line, &line);
+ scalar = gimp_coords_scalarprod (&line, &dcoord) / length2;
+
+ scalar = CLAMP (scalar, 0.0, 1.0);
+
+ /* lines look the same as bezier curves where the handles
+ * sit on the anchors, however, they are parametrized
+ * differently. Hence we have to do some weird approximation. */
+
+ pos1 = pos2 = 0.5;
+
+ for (i = 0; i <= 15; i++)
+ {
+ pos2 *= 0.5;
+
+ if (3 * pos1 * pos1 * (1-pos1) + pos1 * pos1 * pos1 < scalar)
+ pos1 += pos2;
+ else
+ pos1 -= pos2;
+ }
+
+ *ret_pos = pos1;
+
+ gimp_coords_mix (1.0, &(beziercoords[0]),
+ scalar, &line,
+ ret_point);
+
+ gimp_coords_difference (coord, ret_point, &dcoord);
+
+ return gimp_coords_length (&dcoord);
+ }
+
+ /* ok, we have to subdivide */
+
+ subdivided[0] = beziercoords[0];
+ subdivided[6] = beziercoords[3];
+
+ /* if (!depth) g_printerr ("Hit recursion depth limit!\n"); */
+
+ gimp_coords_average (&(beziercoords[0]), &(beziercoords[1]),
+ &(subdivided[1]));
+
+ gimp_coords_average (&(beziercoords[1]), &(beziercoords[2]),
+ &(subdivided[7]));
+
+ gimp_coords_average (&(beziercoords[2]), &(beziercoords[3]),
+ &(subdivided[5]));
+
+ gimp_coords_average (&(subdivided[1]), &(subdivided[7]),
+ &(subdivided[2]));
+
+ gimp_coords_average (&(subdivided[7]), &(subdivided[5]),
+ &(subdivided[4]));
+
+ gimp_coords_average (&(subdivided[2]), &(subdivided[4]),
+ &(subdivided[3]));
+
+ /*
+ * We now have the coordinates of the two bezier segments in
+ * subdivided [0-3] and subdivided [3-6]
+ */
+
+ dist1 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[0]),
+ coord, precision,
+ &point1, &pos1,
+ depth - 1);
+
+ dist2 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[3]),
+ coord, precision,
+ &point2, &pos2,
+ depth - 1);
+
+ if (dist1 <= dist2)
+ {
+ *ret_point = point1;
+ *ret_pos = 0.5 * pos1;
+ return dist1;
+ }
+ else
+ {
+ *ret_point = point2;
+ *ret_pos = 0.5 + 0.5 * pos2;
+ return dist2;
+ }
+}
+
+
+static gdouble
+gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ gdouble min_dist, dist, pos;
+ GimpCoords point;
+ GimpCoords segmentcoords[4];
+ GList *anchorlist;
+ GimpAnchor *segment_start;
+ GimpAnchor *segment_end = NULL;
+ GimpAnchor *anchor;
+ gint count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1.0;
+
+ count = 0;
+ min_dist = -1;
+
+ for (anchorlist = stroke->anchors->head;
+ GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ segment_start = anchorlist->data;
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ segment_end = anchorlist->data;
+ dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords,
+ coord1, coord2,
+ precision,
+ &point, &pos);
+
+ if (dist >= 0 && (dist < min_dist || min_dist < 0))
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (nearest)
+ *nearest = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+
+ segment_start = anchorlist->data;
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ if (stroke->closed && ! g_queue_is_empty (stroke->anchors))
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+
+ anchorlist = g_list_next (anchorlist);
+
+ if (anchorlist)
+ {
+ segment_end = GIMP_ANCHOR (anchorlist->data);
+ segmentcoords[3] = segment_end->position;
+ }
+
+ dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords,
+ coord1, coord2,
+ precision,
+ &point, &pos);
+
+ if (dist >= 0 && (dist < min_dist || min_dist < 0))
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (nearest)
+ *nearest = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+ }
+
+ return min_dist;
+}
+
+static gdouble
+gimp_bezier_stroke_segment_nearest_tangent_get (const GimpCoords *beziercoords,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos)
+{
+ GArray *ret_coords;
+ GArray *ret_params;
+ GimpCoords dir, line, dcoord, min_point;
+ gdouble min_dist = -1;
+ gdouble dist, length2, scalar, ori, ori2;
+ gint i;
+
+ gimp_coords_difference (coord2, coord1, &line);
+
+ ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+ ret_params = g_array_new (FALSE, FALSE, sizeof (gdouble));
+
+ g_printerr ("(%.2f, %.2f)-(%.2f,%.2f): ", coord1->x, coord1->y,
+ coord2->x, coord2->y);
+
+ gimp_coords_interpolate_bezier (beziercoords, precision,
+ ret_coords, ret_params);
+
+ g_return_val_if_fail (ret_coords->len == ret_params->len, -1.0);
+
+ if (ret_coords->len < 2)
+ return -1;
+
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, 1),
+ &g_array_index (ret_coords, GimpCoords, 0),
+ &dir);
+ ori = dir.x * line.y - dir.y * line.x;
+
+ for (i = 2; i < ret_coords->len; i++)
+ {
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
+ &g_array_index (ret_coords, GimpCoords, i-1),
+ &dir);
+ ori2 = dir.x * line.y - dir.y * line.x;
+
+ if (ori * ori2 <= 0)
+ {
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
+ coord1,
+ &dcoord);
+
+ length2 = gimp_coords_scalarprod (&line, &line);
+ scalar = gimp_coords_scalarprod (&line, &dcoord) / length2;
+
+ if (scalar >= 0 && scalar <= 1)
+ {
+ gimp_coords_mix (1.0, coord1,
+ scalar, &line,
+ &min_point);
+ gimp_coords_difference (&min_point,
+ &g_array_index (ret_coords, GimpCoords, i),
+ &dcoord);
+ dist = gimp_coords_length (&dcoord);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+ *ret_point = g_array_index (ret_coords, GimpCoords, i);
+ *ret_pos = g_array_index (ret_params, gdouble, i);
+ }
+ }
+ }
+ ori = ori2;
+ }
+
+ if (min_dist < 0)
+ g_printerr ("-\n");
+ else
+ g_printerr ("%f: (%.2f, %.2f) /%.3f/\n", min_dist,
+ (*ret_point).x, (*ret_point).y, *ret_pos);
+
+ g_array_free (ret_coords, TRUE);
+ g_array_free (ret_params, TRUE);
+
+ return min_dist;
+}
+
+static gboolean
+gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ GList *listneighbor;
+ gint loose_end;
+
+ if (stroke->closed)
+ return FALSE;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return TRUE;
+
+ /* assure that there is a neighbor specified */
+ g_return_val_if_fail (neighbor != NULL, FALSE);
+
+ loose_end = 0;
+ listneighbor = stroke->anchors->tail;
+
+ /* Check if the neighbor is at an end of the control points */
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = 1;
+ }
+ else
+ {
+ listneighbor = g_list_first (stroke->anchors->head);
+
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = -1;
+ }
+ else
+ {
+ /*
+ * It isn't. If we are on a handle go to the nearest
+ * anchor and see if we can find an end from it.
+ * Yes, this is tedious.
+ */
+
+ listneighbor = g_queue_find (stroke->anchors, neighbor);
+
+ if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL)
+ {
+ if (listneighbor->prev &&
+ GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->prev;
+ }
+ else if (listneighbor->next &&
+ GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->next;
+ }
+ else
+ {
+ loose_end = 0;
+ listneighbor = NULL;
+ }
+ }
+
+ if (listneighbor)
+ /* we found a suitable ANCHOR_ANCHOR now, lets
+ * search for its loose end.
+ */
+ {
+ if (listneighbor->prev &&
+ listneighbor->prev->prev == NULL)
+ {
+ loose_end = -1;
+ }
+ else if (listneighbor->next &&
+ listneighbor->next->next == NULL)
+ {
+ loose_end = 1;
+ }
+ }
+ }
+ }
+
+ return (loose_end != 0);
+}
+
+GimpAnchor *
+gimp_bezier_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ GimpAnchor *anchor = NULL;
+ GList *listneighbor;
+ gint loose_end, control_count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ {
+ /* assure that there is no neighbor specified */
+ g_return_val_if_fail (neighbor == NULL, NULL);
+
+ anchor = gimp_anchor_new (GIMP_ANCHOR_CONTROL, coords);
+
+ g_queue_push_tail (stroke->anchors, anchor);
+
+ switch (extend_mode)
+ {
+ case EXTEND_SIMPLE:
+ break;
+
+ case EXTEND_EDITABLE:
+ anchor = gimp_bezier_stroke_extend (stroke,
+ coords, anchor,
+ EXTEND_SIMPLE);
+
+ /* we return the GIMP_ANCHOR_ANCHOR */
+ gimp_bezier_stroke_extend (stroke,
+ coords, anchor,
+ EXTEND_SIMPLE);
+
+ break;
+
+ default:
+ anchor = NULL;
+ }
+
+ return anchor;
+ }
+ else
+ {
+ /* assure that there is a neighbor specified */
+ g_return_val_if_fail (neighbor != NULL, NULL);
+
+ loose_end = 0;
+ listneighbor = stroke->anchors->tail;
+
+ /* Check if the neighbor is at an end of the control points */
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = 1;
+ }
+ else
+ {
+ listneighbor = stroke->anchors->head;
+
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = -1;
+ }
+ else
+ {
+ /*
+ * It isn't. If we are on a handle go to the nearest
+ * anchor and see if we can find an end from it.
+ * Yes, this is tedious.
+ */
+
+ listneighbor = g_queue_find (stroke->anchors, neighbor);
+
+ if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL)
+ {
+ if (listneighbor->prev &&
+ GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->prev;
+ }
+ else if (listneighbor->next &&
+ GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->next;
+ }
+ else
+ {
+ loose_end = 0;
+ listneighbor = NULL;
+ }
+ }
+
+ if (listneighbor)
+ /* we found a suitable ANCHOR_ANCHOR now, lets
+ * search for its loose end.
+ */
+ {
+ if (listneighbor->next &&
+ listneighbor->next->next == NULL)
+ {
+ loose_end = 1;
+ listneighbor = listneighbor->next;
+ }
+ else if (listneighbor->prev &&
+ listneighbor->prev->prev == NULL)
+ {
+ loose_end = -1;
+ listneighbor = listneighbor->prev;
+ }
+ }
+ }
+ }
+
+ if (loose_end)
+ {
+ GimpAnchorType type;
+
+ /* We have to detect the type of the point to add... */
+
+ control_count = 0;
+
+ if (loose_end == 1)
+ {
+ while (listneighbor &&
+ GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ control_count++;
+ listneighbor = listneighbor->prev;
+ }
+ }
+ else
+ {
+ while (listneighbor &&
+ GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ control_count++;
+ listneighbor = listneighbor->next;
+ }
+ }
+
+ switch (extend_mode)
+ {
+ case EXTEND_SIMPLE:
+ switch (control_count)
+ {
+ case 0:
+ type = GIMP_ANCHOR_CONTROL;
+ break;
+ case 1:
+ if (listneighbor) /* only one handle in the path? */
+ type = GIMP_ANCHOR_CONTROL;
+ else
+ type = GIMP_ANCHOR_ANCHOR;
+ break;
+ case 2:
+ type = GIMP_ANCHOR_ANCHOR;
+ break;
+ default:
+ g_warning ("inconsistent bezier curve: "
+ "%d successive control handles", control_count);
+ type = GIMP_ANCHOR_ANCHOR;
+ }
+
+ anchor = gimp_anchor_new (type, coords);
+
+ if (loose_end == 1)
+ g_queue_push_tail (stroke->anchors, anchor);
+
+ if (loose_end == -1)
+ g_queue_push_head (stroke->anchors, anchor);
+ break;
+
+ case EXTEND_EDITABLE:
+ switch (control_count)
+ {
+ case 0:
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ &(neighbor->position),
+ neighbor,
+ EXTEND_SIMPLE);
+ case 1:
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ neighbor,
+ EXTEND_SIMPLE);
+ case 2:
+ anchor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ neighbor,
+ EXTEND_SIMPLE);
+
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ anchor,
+ EXTEND_SIMPLE);
+ break;
+ default:
+ g_warning ("inconsistent bezier curve: "
+ "%d successive control handles", control_count);
+ }
+ }
+
+ return anchor;
+ }
+
+ return NULL;
+ }
+}
+
+static gboolean
+gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ GList *list1;
+ GList *list2;
+
+ list1 = g_queue_find (stroke->anchors, anchor);
+ list1 = gimp_bezier_stroke_get_anchor_listitem (list1);
+ list2 = g_queue_find (extension->anchors, neighbor);
+ list2 = gimp_bezier_stroke_get_anchor_listitem (list2);
+
+ g_return_val_if_fail (list1 != NULL && list2 != NULL, FALSE);
+
+ if (stroke == extension)
+ {
+ g_return_val_if_fail ((list1->prev && list1->prev->prev == NULL &&
+ list2->next && list2->next->next == NULL) ||
+ (list1->next && list1->next->next == NULL &&
+ list2->prev && list2->prev->prev == NULL), FALSE);
+ gimp_stroke_close (stroke);
+ return TRUE;
+ }
+
+ if (list1->prev && list1->prev->prev == NULL)
+ {
+ g_queue_reverse (stroke->anchors);
+ }
+
+ g_return_val_if_fail (list1->next && list1->next->next == NULL, FALSE);
+
+ if (list2->next && list2->next->next == NULL)
+ {
+ g_queue_reverse (extension->anchors);
+ }
+
+ g_return_val_if_fail (list2->prev && list2->prev->prev == NULL, FALSE);
+
+ for (list1 = extension->anchors->head; list1; list1 = g_list_next (list1))
+ g_queue_push_tail (stroke->anchors, list1->data);
+
+ g_queue_clear (extension->anchors);
+
+ return TRUE;
+}
+
+
+static void
+gimp_bezier_stroke_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords delta, coord1, coord2;
+ GList *anchor_list;
+
+ delta = *deltacoord;
+ delta.pressure = 0;
+ delta.xtilt = 0;
+ delta.ytilt = 0;
+ delta.wheel = 0;
+
+ gimp_coords_add (&(anchor->position), &delta, &coord1);
+ anchor->position = coord1;
+
+ anchor_list = g_queue_find (stroke->anchors, anchor);
+ g_return_if_fail (anchor_list != NULL);
+
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (g_list_previous (anchor_list))
+ {
+ coord2 = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
+ gimp_coords_add (&coord2, &delta, &coord1);
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position = coord1;
+ }
+
+ if (g_list_next (anchor_list))
+ {
+ coord2 = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
+ gimp_coords_add (&coord2, &delta, &coord1);
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->position = coord1;
+ }
+ }
+ else
+ {
+ if (feature == GIMP_ANCHOR_FEATURE_SYMMETRIC)
+ {
+ GList *neighbour = NULL, *opposite = NULL;
+
+ /* search for opposite control point. Sigh. */
+ neighbour = g_list_previous (anchor_list);
+ if (neighbour &&
+ GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ opposite = g_list_previous (neighbour);
+ }
+ else
+ {
+ neighbour = g_list_next (anchor_list);
+ if (neighbour &&
+ GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ opposite = g_list_next (neighbour);
+ }
+ }
+ if (opposite &&
+ GIMP_ANCHOR (opposite->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ gimp_coords_difference (&(GIMP_ANCHOR (neighbour->data)->position),
+ &(anchor->position), &delta);
+ gimp_coords_add (&(GIMP_ANCHOR (neighbour->data)->position),
+ &delta, &coord1);
+ GIMP_ANCHOR (opposite->data)->position = coord1;
+ }
+ }
+ }
+}
+
+
+static void
+gimp_bezier_stroke_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords deltacoord;
+
+ gimp_coords_difference (coord, &anchor->position, &deltacoord);
+ gimp_bezier_stroke_anchor_move_relative (stroke, anchor,
+ &deltacoord, feature);
+}
+
+static void
+gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ GList *anchor_list;
+
+ anchor_list = g_queue_find (stroke->anchors, anchor);
+
+ g_return_if_fail (anchor_list != NULL);
+
+ switch (feature)
+ {
+ case GIMP_ANCHOR_FEATURE_EDGE:
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (g_list_previous (anchor_list))
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position =
+ anchor->position;
+
+ if (g_list_next (anchor_list))
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->position =
+ anchor->position;
+ }
+ else
+ {
+ if (g_list_previous (anchor_list) &&
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
+ anchor->position = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
+ if (g_list_next (anchor_list) &&
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
+ anchor->position = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
+ }
+
+ break;
+
+ default:
+ g_warning ("gimp_bezier_stroke_anchor_convert: "
+ "unimplemented anchor conversion %d\n", feature);
+ }
+}
+
+
+static GimpBezierDesc *
+gimp_bezier_stroke_make_bezier (GimpStroke *stroke)
+{
+ GArray *points;
+ GArray *cmd_array;
+ GimpBezierDesc *bezdesc;
+ cairo_path_data_t pathdata;
+ gint num_cmds, i;
+
+ points = gimp_stroke_control_points_get (stroke, NULL);
+
+ g_return_val_if_fail (points && points->len % 3 == 0, NULL);
+ if (points->len < 3)
+ return NULL;
+
+ /* Moveto + (n-1) * curveto + (if closed) curveto + closepath */
+ num_cmds = 2 + (points->len / 3 - 1) * 4;
+ if (stroke->closed)
+ num_cmds += 1 + 4;
+
+ cmd_array = g_array_sized_new (FALSE, FALSE,
+ sizeof (cairo_path_data_t),
+ num_cmds);
+
+ pathdata.header.type = CAIRO_PATH_MOVE_TO;
+ pathdata.header.length = 2;
+ g_array_append_val (cmd_array, pathdata);
+ pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ for (i = 2; i+2 < points->len; i += 3)
+ {
+ pathdata.header.type = CAIRO_PATH_CURVE_TO;
+ pathdata.header.length = 4;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i+1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i+1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i+2).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i+2).position.y;
+ g_array_append_val (cmd_array, pathdata);
+ }
+
+ if (stroke->closed)
+ {
+ pathdata.header.type = CAIRO_PATH_CURVE_TO;
+ pathdata.header.length = 4;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, 0).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 0).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.header.type = CAIRO_PATH_CLOSE_PATH;
+ pathdata.header.length = 1;
+ g_array_append_val (cmd_array, pathdata);
+ }
+
+ if (cmd_array->len != num_cmds)
+ g_printerr ("miscalculated path cmd length! (%d vs. %d)\n",
+ cmd_array->len, num_cmds);
+
+ bezdesc = gimp_bezier_desc_new ((cairo_path_data_t *) cmd_array->data,
+ cmd_array->len);
+ g_array_free (points, TRUE);
+ g_array_free (cmd_array, FALSE);
+
+ return bezdesc;
+}
+
+
+static GArray *
+gimp_bezier_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ GArray *ret_coords;
+ GimpAnchor *anchor;
+ GList *anchorlist;
+ GimpCoords segmentcoords[4];
+ gint count;
+ gboolean need_endpoint = FALSE;
+
+ if (g_queue_is_empty (stroke->anchors))
+ {
+ if (ret_closed)
+ *ret_closed = FALSE;
+ return NULL;
+ }
+
+ ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ count = 0;
+
+ for (anchorlist = stroke->anchors->head;
+ anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ gimp_coords_interpolate_bezier (segmentcoords, precision,
+ ret_coords, NULL);
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ need_endpoint = TRUE;
+ }
+ }
+
+ if (stroke->closed && ! g_queue_is_empty (stroke->anchors))
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+ anchorlist = g_list_next (anchorlist);
+ if (anchorlist)
+ segmentcoords[3] = GIMP_ANCHOR (anchorlist->data)->position;
+
+ gimp_coords_interpolate_bezier (segmentcoords, precision,
+ ret_coords, NULL);
+ need_endpoint = TRUE;
+
+ }
+
+ if (need_endpoint)
+ ret_coords = g_array_append_val (ret_coords, segmentcoords[3]);
+
+ if (ret_closed)
+ *ret_closed = stroke->closed;
+
+ if (ret_coords->len == 0)
+ {
+ g_array_free (ret_coords, TRUE);
+ ret_coords = NULL;
+ }
+
+ return ret_coords;
+}
+
+
+static void
+gimp_bezier_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ GimpStroke *first_stroke = NULL;
+ GimpStroke *last_stroke = NULL;
+ GList *anchorlist;
+ GimpAnchor *anchor;
+ GimpCoords segmentcoords[4];
+ GQueue *transformed[2];
+ gint n_transformed;
+ gint count;
+ gboolean first;
+ gboolean last;
+
+ /* if there's no need for clipping, use the default implementation */
+ if (! ret_strokes ||
+ gimp_matrix3_is_affine (matrix) ||
+ g_queue_is_empty (stroke->anchors))
+ {
+ GIMP_STROKE_CLASS (parent_class)->transform (stroke, matrix, ret_strokes);
+
+ return;
+ }
+
+ /* transform the individual segments */
+ count = 0;
+ first = TRUE;
+ last = FALSE;
+
+ /* find the first non-control anchor */
+ for (anchorlist = stroke->anchors->head;
+ anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ for ( ; anchorlist || stroke->closed; anchorlist = g_list_next (anchorlist))
+ {
+ /* wrap around if 'stroke' is closed, so that we transform the final
+ * segment
+ */
+ if (! anchorlist)
+ {
+ anchorlist = stroke->anchors->head;
+ last = TRUE;
+ }
+
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ gboolean start_in;
+ gboolean end_in;
+ gint i;
+
+ gimp_transform_bezier_coords (matrix, segmentcoords,
+ transformed, &n_transformed,
+ &start_in, &end_in);
+
+ for (i = 0; i < n_transformed; i++)
+ {
+ GimpStroke *s = NULL;
+ GList *list;
+ gint j;
+
+ if (i == 0 && start_in)
+ {
+ /* current stroke is connected to last stroke */
+ s = last_stroke;
+ }
+ else if (last_stroke)
+ {
+ /* current stroke is not connected to last stroke. finalize
+ * last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+ }
+
+ for (list = transformed[i]->head; list; list = g_list_next (list))
+ {
+ GimpCoords *transformedcoords = list->data;
+
+ if (! s)
+ {
+ /* start a new stroke */
+ s = gimp_bezier_stroke_new ();
+
+ g_queue_push_tail (s->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &transformedcoords[0]));
+
+ g_queue_push_tail (ret_strokes, s);
+
+ j = 0;
+ }
+ else
+ {
+ /* continue an existing stroke, skipping the first anchor,
+ * which is the same as the last anchor of the last stroke
+ */
+ j = 1;
+ }
+
+ for (; j < 4; j++)
+ {
+ GimpAnchorType type;
+
+ if (j == 0 || j == 3)
+ type = GIMP_ANCHOR_ANCHOR;
+ else
+ type = GIMP_ANCHOR_CONTROL;
+
+ g_queue_push_tail (s->anchors,
+ gimp_anchor_new (type,
+ &transformedcoords[j]));
+ }
+
+ g_free (transformedcoords);
+ }
+
+ g_queue_free (transformed[i]);
+
+ /* if the current stroke is an initial segment of 'stroke',
+ * remember it, so that we can possibly connect it to the last
+ * stroke later.
+ */
+ if (i == 0 && start_in && first)
+ first_stroke = s;
+
+ last_stroke = s;
+ first = FALSE;
+ }
+
+ if (! end_in && last_stroke)
+ {
+ /* the next stroke is not connected to the last stroke. finalize
+ * the last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+
+ last_stroke = NULL;
+ }
+
+ if (last)
+ break;
+
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ /* if the last stroke is a final segment of 'stroke'... */
+ if (last_stroke)
+ {
+ /* ... and the first stroke is an initial segment of 'stroke', and
+ * 'stroke' is closed ...
+ */
+ if (first_stroke && stroke->closed)
+ {
+ /* connect the first and last strokes */
+
+ /* remove the first anchor, which is a synthetic control point */
+ gimp_anchor_free (g_queue_pop_head (first_stroke->anchors));
+ /* remove the last anchor, which is the same anchor point as the
+ * first anchor
+ */
+ gimp_anchor_free (g_queue_pop_tail (last_stroke->anchors));
+
+ if (first_stroke == last_stroke)
+ {
+ /* the result is a single stroke. move the last anchor, which is
+ * an orphan control point, to the front, to fill in the removed
+ * control point of the first anchor, and close the stroke.
+ */
+ g_queue_push_head (first_stroke->anchors,
+ g_queue_pop_tail (first_stroke->anchors));
+
+ first_stroke->closed = TRUE;
+ }
+ else
+ {
+ /* the result is multiple strokes. prepend the last stroke to
+ * the first stroke, and discard it.
+ */
+ while ((anchor = g_queue_pop_tail (last_stroke->anchors)))
+ g_queue_push_head (first_stroke->anchors, anchor);
+
+ g_object_unref (g_queue_pop_tail (ret_strokes));
+ }
+ }
+ else
+ {
+ /* otherwise, the first and last strokes are not connected. finalize
+ * the last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+ }
+ }
+}
+
+
+GimpStroke *
+gimp_bezier_stroke_new_moveto (const GimpCoords *start)
+{
+ GimpStroke *stroke = gimp_bezier_stroke_new ();
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ start));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ start));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ start));
+ return stroke;
+}
+
+void
+gimp_bezier_stroke_lineto (GimpStroke *stroke,
+ const GimpCoords *end)
+{
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+void
+gimp_bezier_stroke_conicto (GimpStroke *stroke,
+ const GimpCoords *control,
+ const GimpCoords *end)
+{
+ GimpCoords start, coords;
+
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_get_length (stroke->anchors) > 1);
+
+ start = GIMP_ANCHOR (stroke->anchors->tail->prev->data)->position;
+
+ gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, &start, &coords);
+
+ GIMP_ANCHOR (stroke->anchors->tail->data)->position = coords;
+
+ gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, end, &coords);
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &coords));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+void
+gimp_bezier_stroke_cubicto (GimpStroke *stroke,
+ const GimpCoords *control1,
+ const GimpCoords *control2,
+ const GimpCoords *end)
+{
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ GIMP_ANCHOR (stroke->anchors->tail->data)->position = *control1;
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ control2));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+static gdouble
+arcto_circleparam (gdouble h,
+ gdouble *y)
+{
+ gdouble t0 = 0.5;
+ gdouble dt = 0.25;
+ gdouble pt0;
+ gdouble y01, y12, y23, y012, y123, y0123; /* subdividing y[] */
+
+ while (dt >= 0.00001)
+ {
+ pt0 = ( y[0] * (1-t0) * (1-t0) * (1-t0) +
+ 3 * y[1] * (1-t0) * (1-t0) * t0 +
+ 3 * y[2] * (1-t0) * t0 * t0 +
+ y[3] * t0 * t0 * t0 );
+
+ if (pt0 > h)
+ t0 = t0 - dt;
+ else if (pt0 < h)
+ t0 = t0 + dt;
+ else
+ break;
+ dt = dt/2;
+ }
+
+ y01 = y[0] * (1-t0) + y[1] * t0;
+ y12 = y[1] * (1-t0) + y[2] * t0;
+ y23 = y[2] * (1-t0) + y[3] * t0;
+ y012 = y01 * (1-t0) + y12 * t0;
+ y123 = y12 * (1-t0) + y23 * t0;
+ y0123 = y012 * (1-t0) + y123 * t0;
+
+ y[0] = y0123; y[1] = y123; y[2] = y23; /* y[3] unchanged */
+
+ return t0;
+}
+
+static void
+arcto_subdivide (gdouble t,
+ gint part,
+ GimpCoords *p)
+{
+ GimpCoords p01, p12, p23, p012, p123, p0123;
+
+ gimp_coords_mix (1-t, &(p[0]), t, &(p[1]), &p01 );
+ gimp_coords_mix (1-t, &(p[1]), t, &(p[2]), &p12 );
+ gimp_coords_mix (1-t, &(p[2]), t, &(p[3]), &p23 );
+ gimp_coords_mix (1-t, &p01 , t, &p12 , &p012 );
+ gimp_coords_mix (1-t, &p12 , t, &p23 , &p123 );
+ gimp_coords_mix (1-t, &p012 , t, &p123 , &p0123);
+
+ if (part == 0)
+ {
+ /* p[0] unchanged */
+ p[1] = p01;
+ p[2] = p012;
+ p[3] = p0123;
+ }
+ else
+ {
+ p[0] = p0123;
+ p[1] = p123;
+ p[2] = p23;
+ /* p[3] unchanged */
+ }
+}
+
+static void
+arcto_ellipsesegment (gdouble radius_x,
+ gdouble radius_y,
+ gdouble phi0,
+ gdouble phi1,
+ GimpCoords *ellips)
+{
+ const GimpCoords template = GIMP_COORDS_DEFAULT_VALUES;
+ const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0;
+
+ gdouble phi_s, phi_e;
+ gdouble y[4];
+ gdouble h0, h1;
+ gdouble t0, t1;
+
+ g_return_if_fail (ellips != NULL);
+
+ y[0] = 0.0;
+ y[1] = circlemagic;
+ y[2] = 1.0;
+ y[3] = 1.0;
+
+ ellips[0] = template;
+ ellips[1] = template;
+ ellips[2] = template;
+ ellips[3] = template;
+
+ if (phi0 < phi1)
+ {
+ phi_s = floor (phi0 / G_PI_2) * G_PI_2;
+ while (phi_s < 0) phi_s += 2 * G_PI;
+ phi_e = phi_s + G_PI_2;
+ }
+ else
+ {
+ phi_e = floor (phi1 / G_PI_2) * G_PI_2;
+ while (phi_e < 0) phi_e += 2 * G_PI;
+ phi_s = phi_e + G_PI_2;
+ }
+
+ h0 = sin (fabs (phi0-phi_s));
+ h1 = sin (fabs (phi1-phi_s));
+
+ ellips[0].x = cos (phi_s); ellips[0].y = sin (phi_s);
+ ellips[3].x = cos (phi_e); ellips[3].y = sin (phi_e);
+
+ gimp_coords_mix (1, &(ellips[0]), circlemagic, &(ellips[3]), &(ellips[1]));
+ gimp_coords_mix (circlemagic, &(ellips[0]), 1, &(ellips[3]), &(ellips[2]));
+
+ if (h0 > y[0])
+ {
+ t0 = arcto_circleparam (h0, y); /* also subdivides y[] at t0 */
+ arcto_subdivide (t0, 1, ellips);
+ }
+
+ if (h1 < y[3])
+ {
+ t1 = arcto_circleparam (h1, y);
+ arcto_subdivide (t1, 0, ellips);
+ }
+
+ ellips[0].x *= radius_x ; ellips[0].y *= radius_y;
+ ellips[1].x *= radius_x ; ellips[1].y *= radius_y;
+ ellips[2].x *= radius_x ; ellips[2].y *= radius_y;
+ ellips[3].x *= radius_x ; ellips[3].y *= radius_y;
+}
+
+void
+gimp_bezier_stroke_arcto (GimpStroke *bez_stroke,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle_rad,
+ gboolean large_arc,
+ gboolean sweep,
+ const GimpCoords *end)
+{
+ GimpCoords start;
+ GimpCoords middle; /* between start and end */
+ GimpCoords trans_delta;
+ GimpCoords trans_center;
+ GimpCoords tmp_center;
+ GimpCoords center;
+ GimpCoords ellips[4]; /* control points of untransformed ellipse segment */
+ GimpCoords ctrl[4]; /* control points of next bezier segment */
+
+ GimpMatrix3 anglerot;
+
+ gdouble lambda;
+ gdouble phi0, phi1, phi2;
+ gdouble tmpx, tmpy;
+
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (bez_stroke));
+ g_return_if_fail (bez_stroke->closed == FALSE);
+ g_return_if_fail (g_queue_get_length (bez_stroke->anchors) > 1);
+
+ if (radius_x == 0 || radius_y == 0)
+ {
+ gimp_bezier_stroke_lineto (bez_stroke, end);
+ return;
+ }
+
+ start = GIMP_ANCHOR (bez_stroke->anchors->tail->prev->data)->position;
+
+ gimp_matrix3_identity (&anglerot);
+ gimp_matrix3_rotate (&anglerot, -angle_rad);
+
+ gimp_coords_mix (0.5, &start, -0.5, end, &trans_delta);
+ gimp_matrix3_transform_point (&anglerot,
+ trans_delta.x, trans_delta.y,
+ &tmpx, &tmpy);
+ trans_delta.x = tmpx;
+ trans_delta.y = tmpy;
+
+ lambda = (SQR (trans_delta.x) / SQR (radius_x) +
+ SQR (trans_delta.y) / SQR (radius_y));
+
+ if (lambda < 0.00001)
+ {
+ /* don't bother with it - endpoint is too close to startpoint */
+ return;
+ }
+
+ trans_center = trans_delta;
+
+ if (lambda > 1.0)
+ {
+ /* The radii are too small for a matching ellipse. We expand them
+ * so that they fit exactly (center of the ellipse between the
+ * start- and endpoint
+ */
+ radius_x *= sqrt (lambda);
+ radius_y *= sqrt (lambda);
+ trans_center.x = 0.0;
+ trans_center.y = 0.0;
+ }
+ else
+ {
+ gdouble factor = sqrt ((1.0 - lambda) / lambda);
+
+ trans_center.x = trans_delta.y * radius_x / radius_y * factor;
+ trans_center.y = - trans_delta.x * radius_y / radius_x * factor;
+ }
+
+ if ((large_arc && sweep) || (!large_arc && !sweep))
+ {
+ trans_center.x *= -1;
+ trans_center.y *= -1;
+ }
+
+ gimp_matrix3_identity (&anglerot);
+ gimp_matrix3_rotate (&anglerot, angle_rad);
+
+ tmp_center = trans_center;
+ gimp_matrix3_transform_point (&anglerot,
+ tmp_center.x, tmp_center.y,
+ &tmpx, &tmpy);
+ tmp_center.x = tmpx;
+ tmp_center.y = tmpy;
+
+ gimp_coords_mix (0.5, &start, 0.5, end, &middle);
+ gimp_coords_add (&tmp_center, &middle, &center);
+
+ phi1 = atan2 ((trans_delta.y - trans_center.y) / radius_y,
+ (trans_delta.x - trans_center.x) / radius_x);
+
+ phi2 = atan2 ((- trans_delta.y - trans_center.y) / radius_y,
+ (- trans_delta.x - trans_center.x) / radius_x);
+
+ if (phi1 < 0)
+ phi1 += 2 * G_PI;
+
+ if (phi2 < 0)
+ phi2 += 2 * G_PI;
+
+ if (sweep)
+ {
+ while (phi2 < phi1)
+ phi2 += 2 * G_PI;
+
+ phi0 = floor (phi1 / G_PI_2) * G_PI_2;
+
+ while (phi0 < phi2)
+ {
+ arcto_ellipsesegment (radius_x, radius_y,
+ MAX (phi0, phi1), MIN (phi0 + G_PI_2, phi2),
+ ellips);
+
+ gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
+ &tmpx, &tmpy);
+ ellips[0].x = tmpx; ellips[0].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
+ &tmpx, &tmpy);
+ ellips[1].x = tmpx; ellips[1].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
+ &tmpx, &tmpy);
+ ellips[2].x = tmpx; ellips[2].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
+ &tmpx, &tmpy);
+ ellips[3].x = tmpx; ellips[3].y = tmpy;
+
+ gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
+ gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
+ gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
+
+ gimp_bezier_stroke_cubicto (bez_stroke,
+ &(ctrl[1]), &(ctrl[2]), &(ctrl[3]));
+ phi0 += G_PI_2;
+ }
+ }
+ else
+ {
+ while (phi1 < phi2)
+ phi1 += 2 * G_PI;
+
+ phi0 = ceil (phi1 / G_PI_2) * G_PI_2;
+
+ while (phi0 > phi2)
+ {
+ arcto_ellipsesegment (radius_x, radius_y,
+ MIN (phi0, phi1), MAX (phi0 - G_PI_2, phi2),
+ ellips);
+
+ gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
+ &tmpx, &tmpy);
+ ellips[0].x = tmpx; ellips[0].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
+ &tmpx, &tmpy);
+ ellips[1].x = tmpx; ellips[1].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
+ &tmpx, &tmpy);
+ ellips[2].x = tmpx; ellips[2].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
+ &tmpx, &tmpy);
+ ellips[3].x = tmpx; ellips[3].y = tmpy;
+
+ gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
+ gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
+ gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
+
+ gimp_bezier_stroke_cubicto (bez_stroke,
+ &(ctrl[1]), &(ctrl[2]), &(ctrl[3]));
+ phi0 -= G_PI_2;
+ }
+ }
+}
+
+GimpStroke *
+gimp_bezier_stroke_new_ellipse (const GimpCoords *center,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle)
+{
+ GimpStroke *stroke;
+ GimpCoords p1 = *center;
+ GimpCoords p2 = *center;
+ GimpCoords p3 = *center;
+ GimpCoords dx = { 0, };
+ GimpCoords dy = { 0, };
+ const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0;
+ GimpAnchor *handle;
+
+ dx.x = radius_x * cos (angle);
+ dx.y = - radius_x * sin (angle);
+ dy.x = radius_y * sin (angle);
+ dy.y = radius_y * cos (angle);
+
+ gimp_coords_mix (1.0, center, 1.0, &dx, &p1);
+ stroke = gimp_bezier_stroke_new_moveto (&p1);
+
+ handle = g_queue_peek_head (stroke->anchors);
+ gimp_coords_mix (1.0, &p1, -circlemagic, &dy, &handle->position);
+
+ gimp_coords_mix (1.0, &p1, circlemagic, &dy, &p1);
+ gimp_coords_mix (1.0, center, 1.0, &dy, &p3);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dx, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p1);
+ gimp_coords_mix (1.0, center, -1.0, &dx, &p3);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dy, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dy, &p1);
+ gimp_coords_mix (1.0, center, -1.0, &dy, &p3);
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ handle = g_queue_peek_tail (stroke->anchors);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dx, &handle->position);
+
+ gimp_stroke_close (stroke);
+
+ return stroke;
+}
+
+
+/* helper function to get the associated anchor of a listitem */
+
+static GList *
+gimp_bezier_stroke_get_anchor_listitem (GList *list)
+{
+ if (!list)
+ return NULL;
+
+ if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list;
+
+ if (list->prev && GIMP_ANCHOR (list->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list->prev;
+
+ if (list->next && GIMP_ANCHOR (list->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list->next;
+
+ g_return_val_if_fail (/* bezier stroke inconsistent! */ FALSE, NULL);
+
+ return NULL;
+}
diff --git a/app/vectors/gimpbezierstroke.h b/app/vectors/gimpbezierstroke.h
new file mode 100644
index 0000000..2fe6b98
--- /dev/null
+++ b/app/vectors/gimpbezierstroke.h
@@ -0,0 +1,84 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbezierstroke.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BEZIER_STROKE_H__
+#define __GIMP_BEZIER_STROKE_H__
+
+#include "gimpstroke.h"
+
+
+#define GIMP_TYPE_BEZIER_STROKE (gimp_bezier_stroke_get_type ())
+#define GIMP_BEZIER_STROKE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BEZIER_STROKE, GimpBezierStroke))
+#define GIMP_BEZIER_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BEZIER_STROKE, GimpBezierStrokeClass))
+#define GIMP_IS_BEZIER_STROKE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BEZIER_STROKE))
+#define GIMP_IS_BEZIER_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BEZIER_STROKE))
+#define GIMP_BEZIER_STROKE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BEZIER_STROKE, GimpBezierStrokeClass))
+
+
+typedef struct _GimpBezierStrokeClass GimpBezierStrokeClass;
+
+struct _GimpBezierStroke
+{
+ GimpStroke parent_instance;
+};
+
+struct _GimpBezierStrokeClass
+{
+ GimpStrokeClass parent_class;
+};
+
+
+GType gimp_bezier_stroke_get_type (void) G_GNUC_CONST;
+
+GimpStroke * gimp_bezier_stroke_new (void);
+GimpStroke * gimp_bezier_stroke_new_from_coords (const GimpCoords *coords,
+ gint n_coords,
+ gboolean closed);
+
+GimpStroke * gimp_bezier_stroke_new_moveto (const GimpCoords *start);
+void gimp_bezier_stroke_lineto (GimpStroke *bez_stroke,
+ const GimpCoords *end);
+void gimp_bezier_stroke_conicto (GimpStroke *bez_stroke,
+ const GimpCoords *control,
+ const GimpCoords *end);
+void gimp_bezier_stroke_cubicto (GimpStroke *bez_stroke,
+ const GimpCoords *control1,
+ const GimpCoords *control2,
+ const GimpCoords *end);
+void gimp_bezier_stroke_arcto (GimpStroke *bez_stroke,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle_rad,
+ gboolean large_arc,
+ gboolean sweep,
+ const GimpCoords *end);
+GimpStroke * gimp_bezier_stroke_new_ellipse (const GimpCoords *center,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle);
+
+
+GimpAnchor * gimp_bezier_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode);
+
+
+#endif /* __GIMP_BEZIER_STROKE_H__ */
diff --git a/app/vectors/gimpstroke-new.c b/app/vectors/gimpstroke-new.c
new file mode 100644
index 0000000..daba683
--- /dev/null
+++ b/app/vectors/gimpstroke-new.c
@@ -0,0 +1,47 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpstroke-new.c
+ * Copyright (C) 2006 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "vectors-types.h"
+
+#include "gimpstroke-new.h"
+#include "gimpbezierstroke.h"
+
+
+GimpStroke *
+gimp_stroke_new_from_coords (GimpVectorsStrokeType type,
+ const GimpCoords *coords,
+ gint n_coords,
+ gboolean closed)
+{
+ switch (type)
+ {
+ case GIMP_VECTORS_STROKE_TYPE_BEZIER:
+ return gimp_bezier_stroke_new_from_coords (coords, n_coords, closed);
+ break;
+ default:
+ g_warning ("unknown type in gimp_stroke_new_from_coords(): %d", type);
+ return NULL;
+ }
+}
+
diff --git a/app/vectors/gimpstroke-new.h b/app/vectors/gimpstroke-new.h
new file mode 100644
index 0000000..5365cc5
--- /dev/null
+++ b/app/vectors/gimpstroke-new.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpstroke-new.c
+ * Copyright (C) 2006 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_NEW_H__
+#define __GIMP_VECTORS_NEW_H__
+
+
+GimpStroke * gimp_stroke_new_from_coords (GimpVectorsStrokeType type,
+ const GimpCoords *coords,
+ gint n_coords,
+ gboolean closed);
+
+
+#endif /* __GIMP_VECTORS_NEW_H__ */
diff --git a/app/vectors/gimpstroke.c b/app/vectors/gimpstroke.c
new file mode 100644
index 0000000..3a3ab90
--- /dev/null
+++ b/app/vectors/gimpstroke.c
@@ -0,0 +1,1431 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpstroke.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "core/gimp-memsize.h"
+#include "core/gimpcoords.h"
+#include "core/gimpparamspecs.h"
+#include "core/gimp-transform-utils.h"
+
+#include "gimpanchor.h"
+#include "gimpstroke.h"
+
+enum
+{
+ PROP_0,
+ PROP_CONTROL_POINTS,
+ PROP_CLOSED
+};
+
+/* Prototypes */
+
+static void gimp_stroke_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_stroke_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_stroke_finalize (GObject *object);
+
+static gint64 gimp_stroke_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static GimpAnchor * gimp_stroke_real_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord);
+static GimpAnchor * gimp_stroke_real_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev);
+static void gimp_stroke_real_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive);
+static void gimp_stroke_real_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor);
+static gboolean gimp_stroke_real_point_is_movable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static void gimp_stroke_real_point_move_relative
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_point_move_absolute
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+static void gimp_stroke_real_close (GimpStroke *stroke);
+static GimpStroke * gimp_stroke_real_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+static gboolean gimp_stroke_real_anchor_is_insertable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static GimpAnchor * gimp_stroke_real_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+
+static gboolean gimp_stroke_real_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+
+static GimpAnchor * gimp_stroke_real_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode);
+
+gboolean gimp_stroke_real_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+
+
+static gboolean gimp_stroke_real_is_empty (GimpStroke *stroke);
+
+static gdouble gimp_stroke_real_get_length (GimpStroke *stroke,
+ gdouble precision);
+static gdouble gimp_stroke_real_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord);
+static GArray * gimp_stroke_real_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *closed);
+static GimpStroke * gimp_stroke_real_duplicate (GimpStroke *stroke);
+static GimpBezierDesc * gimp_stroke_real_make_bezier (GimpStroke *stroke);
+
+static void gimp_stroke_real_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y);
+static void gimp_stroke_real_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y);
+static void gimp_stroke_real_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle);
+static void gimp_stroke_real_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis);
+static void gimp_stroke_real_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+static void gimp_stroke_real_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+static GList * gimp_stroke_real_get_draw_anchors (GimpStroke *stroke);
+static GList * gimp_stroke_real_get_draw_controls (GimpStroke *stroke);
+static GArray * gimp_stroke_real_get_draw_lines (GimpStroke *stroke);
+static GArray * gimp_stroke_real_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed);
+static gboolean gimp_stroke_real_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope);
+
+
+G_DEFINE_TYPE (GimpStroke, gimp_stroke, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_stroke_parent_class
+
+
+static void
+gimp_stroke_class_init (GimpStrokeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->finalize = gimp_stroke_finalize;
+ object_class->get_property = gimp_stroke_get_property;
+ object_class->set_property = gimp_stroke_set_property;
+
+ gimp_object_class->get_memsize = gimp_stroke_get_memsize;
+
+ klass->changed = NULL;
+ klass->removed = NULL;
+
+ klass->anchor_get = gimp_stroke_real_anchor_get;
+ klass->anchor_get_next = gimp_stroke_real_anchor_get_next;
+ klass->anchor_select = gimp_stroke_real_anchor_select;
+ klass->anchor_move_relative = gimp_stroke_real_anchor_move_relative;
+ klass->anchor_move_absolute = gimp_stroke_real_anchor_move_absolute;
+ klass->anchor_convert = gimp_stroke_real_anchor_convert;
+ klass->anchor_delete = gimp_stroke_real_anchor_delete;
+
+ klass->point_is_movable = gimp_stroke_real_point_is_movable;
+ klass->point_move_relative = gimp_stroke_real_point_move_relative;
+ klass->point_move_absolute = gimp_stroke_real_point_move_absolute;
+
+ klass->nearest_point_get = NULL;
+ klass->nearest_tangent_get = NULL;
+ klass->nearest_intersection_get = NULL;
+ klass->close = gimp_stroke_real_close;
+ klass->open = gimp_stroke_real_open;
+ klass->anchor_is_insertable = gimp_stroke_real_anchor_is_insertable;
+ klass->anchor_insert = gimp_stroke_real_anchor_insert;
+ klass->is_extendable = gimp_stroke_real_is_extendable;
+ klass->extend = gimp_stroke_real_extend;
+ klass->connect_stroke = gimp_stroke_real_connect_stroke;
+
+ klass->is_empty = gimp_stroke_real_is_empty;
+ klass->get_length = gimp_stroke_real_get_length;
+ klass->get_distance = gimp_stroke_real_get_distance;
+ klass->get_point_at_dist = gimp_stroke_real_get_point_at_dist;
+ klass->interpolate = gimp_stroke_real_interpolate;
+
+ klass->duplicate = gimp_stroke_real_duplicate;
+ klass->make_bezier = gimp_stroke_real_make_bezier;
+
+ klass->translate = gimp_stroke_real_translate;
+ klass->scale = gimp_stroke_real_scale;
+ klass->rotate = gimp_stroke_real_rotate;
+ klass->flip = gimp_stroke_real_flip;
+ klass->flip_free = gimp_stroke_real_flip_free;
+ klass->transform = gimp_stroke_real_transform;
+
+
+ klass->get_draw_anchors = gimp_stroke_real_get_draw_anchors;
+ klass->get_draw_controls = gimp_stroke_real_get_draw_controls;
+ klass->get_draw_lines = gimp_stroke_real_get_draw_lines;
+ klass->control_points_get = gimp_stroke_real_control_points_get;
+
+ param_spec = g_param_spec_boxed ("gimp-anchor",
+ "Gimp Anchor",
+ "The control points of a Stroke",
+ GIMP_TYPE_ANCHOR,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_CONTROL_POINTS,
+ gimp_param_spec_value_array ("control-points",
+ "Control Points",
+ "This is an ValueArray "
+ "with the initial "
+ "control points of "
+ "the new Stroke",
+ param_spec,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CLOSED,
+ g_param_spec_boolean ("closed",
+ "Close Flag",
+ "this flag indicates "
+ "whether the stroke "
+ "is closed or not",
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_stroke_init (GimpStroke *stroke)
+{
+ stroke->anchors = g_queue_new ();
+}
+
+static void
+gimp_stroke_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+ GimpValueArray *val_array;
+ gint length;
+ gint i;
+
+ switch (property_id)
+ {
+ case PROP_CLOSED:
+ stroke->closed = g_value_get_boolean (value);
+ break;
+
+ case PROP_CONTROL_POINTS:
+ g_return_if_fail (g_queue_is_empty (stroke->anchors));
+ g_return_if_fail (value != NULL);
+
+ val_array = g_value_get_boxed (value);
+
+ if (val_array == NULL)
+ return;
+
+ length = gimp_value_array_length (val_array);
+
+ for (i = 0; i < length; i++)
+ {
+ GValue *item = gimp_value_array_index (val_array, i);
+
+ g_return_if_fail (G_VALUE_HOLDS (item, GIMP_TYPE_ANCHOR));
+ g_queue_push_tail (stroke->anchors, g_value_dup_boxed (item));
+ }
+
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_stroke_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+
+ switch (property_id)
+ {
+ case PROP_CLOSED:
+ g_value_set_boolean (value, stroke->closed);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_stroke_finalize (GObject *object)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+
+ g_queue_free_full (stroke->anchors, (GDestroyNotify) gimp_anchor_free);
+ stroke->anchors = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_stroke_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_queue_get_memsize (stroke->anchors, sizeof (GimpAnchor));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+void
+gimp_stroke_set_ID (GimpStroke *stroke,
+ gint id)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (stroke->ID == 0 /* we don't want changing IDs... */);
+
+ stroke->ID = id;
+}
+
+gint
+gimp_stroke_get_ID (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), -1);
+
+ return stroke->ID;
+}
+
+
+GimpAnchor *
+gimp_stroke_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_get (stroke, coord);
+}
+
+
+gdouble
+gimp_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ const gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coord != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get (stroke,
+ coord,
+ precision,
+ ret_point,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+gdouble
+gimp_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *coords2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coords1 != NULL, FALSE);
+ g_return_val_if_fail (coords2 != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get (stroke,
+ coords1,
+ coords2,
+ precision,
+ nearest,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+gdouble
+gimp_stroke_nearest_intersection_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *direction,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coords1 != NULL, FALSE);
+ g_return_val_if_fail (direction != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get (stroke,
+ coords1,
+ direction,
+ precision,
+ nearest,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ gdouble dx, dy;
+ gdouble mindist = -1;
+ GList *anchors;
+ GList *list;
+ GimpAnchor *anchor = NULL;
+
+ anchors = gimp_stroke_get_draw_controls (stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
+ dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
+
+ if (mindist < 0 || mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ anchor = GIMP_ANCHOR (list->data);
+ }
+ }
+
+ g_list_free (anchors);
+
+ anchors = gimp_stroke_get_draw_anchors (stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
+ dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
+
+ if (mindist < 0 || mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ anchor = GIMP_ANCHOR (list->data);
+ }
+ }
+
+ g_list_free (anchors);
+
+ return anchor;
+}
+
+
+GimpAnchor *
+gimp_stroke_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_get_next (stroke, prev);
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev)
+{
+ GList *list;
+
+ if (prev)
+ {
+ list = g_queue_find (stroke->anchors, prev);
+ if (list)
+ list = g_list_next (list);
+ }
+ else
+ {
+ list = stroke->anchors->head;
+ }
+
+ if (list)
+ return GIMP_ANCHOR (list->data);
+
+ return NULL;
+}
+
+
+void
+gimp_stroke_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_select (stroke, anchor,
+ selected, exclusive);
+}
+
+static void
+gimp_stroke_real_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive)
+{
+ GList *list = stroke->anchors->head;
+
+ if (exclusive)
+ {
+ while (list)
+ {
+ GIMP_ANCHOR (list->data)->selected = FALSE;
+ list = g_list_next (list);
+ }
+ }
+
+ list = g_queue_find (stroke->anchors, anchor);
+
+ if (list)
+ GIMP_ANCHOR (list->data)->selected = selected;
+}
+
+
+void
+gimp_stroke_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor != NULL);
+ g_return_if_fail (g_queue_find (stroke->anchors, anchor));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_move_relative (stroke, anchor,
+ delta, feature);
+}
+
+static void
+gimp_stroke_real_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature)
+{
+ anchor->position.x += delta->x;
+ anchor->position.y += delta->y;
+}
+
+
+void
+gimp_stroke_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor != NULL);
+ g_return_if_fail (g_queue_find (stroke->anchors, anchor));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_move_absolute (stroke, anchor,
+ coord, feature);
+}
+
+static void
+gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ anchor->position.x = coord->x;
+ anchor->position.y = coord->y;
+}
+
+gboolean
+gimp_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->point_is_movable (stroke, predec,
+ position);
+}
+
+
+static gboolean
+gimp_stroke_real_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return FALSE;
+}
+
+
+void
+gimp_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->point_move_relative (stroke, predec,
+ position, deltacoord,
+ feature);
+}
+
+
+static void
+gimp_stroke_real_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_point_move_relative: default implementation\n");
+}
+
+
+void
+gimp_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->point_move_absolute (stroke, predec,
+ position, coord,
+ feature);
+}
+
+static void
+gimp_stroke_real_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_point_move_absolute: default implementation\n");
+}
+
+
+void
+gimp_stroke_close (GimpStroke *stroke)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ GIMP_STROKE_GET_CLASS (stroke)->close (stroke);
+}
+
+static void
+gimp_stroke_real_close (GimpStroke *stroke)
+{
+ stroke->closed = TRUE;
+ g_object_notify (G_OBJECT (stroke), "closed");
+}
+
+
+void
+gimp_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_convert (stroke, anchor, feature);
+}
+
+static void
+gimp_stroke_real_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_anchor_convert: default implementation\n");
+}
+
+
+void
+gimp_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor && anchor->type == GIMP_ANCHOR_ANCHOR);
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_delete (stroke, anchor);
+}
+
+static void
+gimp_stroke_real_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ g_printerr ("gimp_stroke_anchor_delete: default implementation\n");
+}
+
+GimpStroke *
+gimp_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (end_anchor &&
+ end_anchor->type == GIMP_ANCHOR_ANCHOR, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->open (stroke, end_anchor);
+}
+
+static GimpStroke *
+gimp_stroke_real_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ g_printerr ("gimp_stroke_open: default implementation\n");
+ return NULL;
+}
+
+gboolean
+gimp_stroke_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_is_insertable (stroke,
+ predec,
+ position);
+}
+
+static gboolean
+gimp_stroke_real_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return FALSE;
+}
+
+GimpAnchor *
+gimp_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (predec->type == GIMP_ANCHOR_ANCHOR, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_insert (stroke,
+ predec, position);
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return NULL;
+}
+
+
+gboolean
+gimp_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->is_extendable (stroke, neighbor);
+}
+
+static gboolean
+gimp_stroke_real_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ return FALSE;
+}
+
+
+GimpAnchor *
+gimp_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (!stroke->closed, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->extend (stroke, coords,
+ neighbor, extend_mode);
+}
+
+static GimpAnchor *
+gimp_stroke_real_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ g_printerr ("gimp_stroke_extend: default implementation\n");
+ return NULL;
+}
+
+gboolean
+gimp_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (GIMP_IS_STROKE (extension), FALSE);
+ g_return_val_if_fail (stroke->closed == FALSE &&
+ extension->closed == FALSE, FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->connect_stroke (stroke, anchor,
+ extension, neighbor);
+}
+
+gboolean
+gimp_stroke_real_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ g_printerr ("gimp_stroke_connect_stroke: default implementation\n");
+ return FALSE;
+}
+
+gboolean
+gimp_stroke_is_empty (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->is_empty (stroke);
+}
+
+static gboolean
+gimp_stroke_real_is_empty (GimpStroke *stroke)
+{
+ return g_queue_is_empty (stroke->anchors);
+}
+
+
+gdouble
+gimp_stroke_get_length (GimpStroke *stroke,
+ gdouble precision)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_length (stroke, precision);
+}
+
+static gdouble
+gimp_stroke_real_get_length (GimpStroke *stroke,
+ gdouble precision)
+{
+ GArray *points;
+ gint i;
+ gdouble length;
+ GimpCoords difference;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1;
+
+ points = gimp_stroke_interpolate (stroke, precision, NULL);
+ if (points == NULL)
+ return -1;
+
+ length = 0;
+
+ for (i = 0; i < points->len - 1; i++ )
+ {
+ gimp_coords_difference (&(g_array_index (points, GimpCoords, i)),
+ &(g_array_index (points, GimpCoords, i+1)),
+ &difference);
+ length += gimp_coords_length (&difference);
+ }
+
+ g_array_free(points, TRUE);
+
+ return length;
+}
+
+
+gdouble
+gimp_stroke_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_distance (stroke, coord);
+}
+
+static gdouble
+gimp_stroke_real_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_printerr ("gimp_stroke_get_distance: default implementation\n");
+
+ return 0.0;
+}
+
+
+GArray *
+gimp_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->interpolate (stroke, precision,
+ ret_closed);
+}
+
+static GArray *
+gimp_stroke_real_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ g_printerr ("gimp_stroke_interpolate: default implementation\n");
+
+ return NULL;
+}
+
+GimpStroke *
+gimp_stroke_duplicate (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->duplicate (stroke);
+}
+
+static GimpStroke *
+gimp_stroke_real_duplicate (GimpStroke *stroke)
+{
+ GimpStroke *new_stroke;
+ GList *list;
+
+ new_stroke = g_object_new (G_TYPE_FROM_INSTANCE (stroke),
+ "name", gimp_object_get_name (stroke),
+ NULL);
+
+ g_queue_free_full (new_stroke->anchors, (GDestroyNotify) gimp_anchor_free);
+ new_stroke->anchors = g_queue_copy (stroke->anchors);
+
+ for (list = new_stroke->anchors->head; list; list = g_list_next (list))
+ {
+ list->data = gimp_anchor_copy (GIMP_ANCHOR (list->data));
+ }
+
+ new_stroke->closed = stroke->closed;
+ /* we do *not* copy the ID! */
+
+ return new_stroke;
+}
+
+
+GimpBezierDesc *
+gimp_stroke_make_bezier (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->make_bezier (stroke);
+}
+
+static GimpBezierDesc *
+gimp_stroke_real_make_bezier (GimpStroke *stroke)
+{
+ g_printerr ("gimp_stroke_make_bezier: default implementation\n");
+
+ return NULL;
+}
+
+
+void
+gimp_stroke_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->translate (stroke, offset_x, offset_y);
+}
+
+static void
+gimp_stroke_real_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ anchor->position.x += offset_x;
+ anchor->position.y += offset_y;
+ }
+}
+
+
+void
+gimp_stroke_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->scale (stroke, scale_x, scale_y);
+}
+
+static void
+gimp_stroke_real_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ anchor->position.x *= scale_x;
+ anchor->position.y *= scale_y;
+ }
+}
+
+void
+gimp_stroke_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->rotate (stroke, center_x, center_y, angle);
+}
+
+static void
+gimp_stroke_real_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle)
+{
+ GimpMatrix3 matrix;
+
+ angle = angle / 180.0 * G_PI;
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_rotate_center (&matrix, center_x, center_y, angle);
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+void
+gimp_stroke_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->flip (stroke, flip_type, axis);
+}
+
+static void
+gimp_stroke_real_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_flip (&matrix, flip_type, axis);
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+void
+gimp_stroke_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->flip_free (stroke, x1, y1, x2, y2);
+}
+
+static void
+gimp_stroke_real_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ /* x, y, width and height parameter in gimp_transform_matrix_flip_free are unused */
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_flip_free (&matrix, x1, y1, x2, y2);
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+/* transforms 'stroke' by 'matrix'. due to clipping, the transformation may
+ * result in multiple strokes.
+ *
+ * if 'ret_strokes' is not NULL, the transformed strokes are appended to the
+ * queue, and 'stroke' is left in an unspecified state. one of the resulting
+ * strokes may alias 'stroke'.
+ *
+ * if 'ret_strokes' is NULL, the transformation is performed in-place. if the
+ * transformation results in multiple strokes (which, atm, can only happen for
+ * non-affine transformation), the result is undefined.
+ */
+void
+gimp_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->transform (stroke, matrix, ret_strokes);
+}
+
+static void
+gimp_stroke_real_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ gimp_matrix3_transform_point (matrix,
+ anchor->position.x,
+ anchor->position.y,
+ &anchor->position.x,
+ &anchor->position.y);
+ }
+
+ if (ret_strokes)
+ {
+ stroke->ID = 0;
+
+ g_queue_push_tail (ret_strokes, g_object_ref (stroke));
+ }
+}
+
+
+GList *
+gimp_stroke_get_draw_anchors (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_anchors (stroke);
+}
+
+static GList *
+gimp_stroke_real_get_draw_anchors (GimpStroke *stroke)
+{
+ GList *list;
+ GList *ret_list = NULL;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR)
+ ret_list = g_list_prepend (ret_list, list->data);
+ }
+
+ return g_list_reverse (ret_list);
+}
+
+
+GList *
+gimp_stroke_get_draw_controls (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_controls (stroke);
+}
+
+static GList *
+gimp_stroke_real_get_draw_controls (GimpStroke *stroke)
+{
+ GList *list;
+ GList *ret_list = NULL;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ if (anchor->type == GIMP_ANCHOR_CONTROL)
+ {
+ GimpAnchor *next = list->next ? list->next->data : NULL;
+ GimpAnchor *prev = list->prev ? list->prev->data : NULL;
+
+ if (next && next->type == GIMP_ANCHOR_ANCHOR && next->selected)
+ {
+ /* Ok, this is a hack.
+ * The idea is to give control points at the end of a
+ * stroke a higher priority for the interactive tool.
+ */
+ if (prev)
+ ret_list = g_list_prepend (ret_list, anchor);
+ else
+ ret_list = g_list_append (ret_list, anchor);
+ }
+ else if (prev && prev->type == GIMP_ANCHOR_ANCHOR && prev->selected)
+ {
+ /* same here... */
+ if (next)
+ ret_list = g_list_prepend (ret_list, anchor);
+ else
+ ret_list = g_list_append (ret_list, anchor);
+ }
+ }
+ }
+
+ return g_list_reverse (ret_list);
+}
+
+
+GArray *
+gimp_stroke_get_draw_lines (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_lines (stroke);
+}
+
+static GArray *
+gimp_stroke_real_get_draw_lines (GimpStroke *stroke)
+{
+ GList *list;
+ GArray *ret_lines = NULL;
+ gint count = 0;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ if (anchor->type == GIMP_ANCHOR_ANCHOR && anchor->selected)
+ {
+ if (list->next)
+ {
+ GimpAnchor *next = list->next->data;
+
+ if (count == 0)
+ ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ ret_lines = g_array_append_val (ret_lines, anchor->position);
+ ret_lines = g_array_append_val (ret_lines, next->position);
+ count += 1;
+ }
+
+ if (list->prev)
+ {
+ GimpAnchor *prev = list->prev->data;
+
+ if (count == 0)
+ ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ ret_lines = g_array_append_val (ret_lines, anchor->position);
+ ret_lines = g_array_append_val (ret_lines, prev->position);
+ count += 1;
+ }
+ }
+ }
+
+ return ret_lines;
+}
+
+GArray *
+gimp_stroke_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->control_points_get (stroke,
+ ret_closed);
+}
+
+static GArray *
+gimp_stroke_real_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed)
+{
+ guint num_anchors;
+ GArray *ret_array;
+ GList *list;
+
+ num_anchors = g_queue_get_length (stroke->anchors);
+ ret_array = g_array_sized_new (FALSE, FALSE,
+ sizeof (GimpAnchor), num_anchors);
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ g_array_append_vals (ret_array, list->data, 1);
+ }
+
+ if (ret_closed)
+ *ret_closed = stroke->closed;
+
+ return ret_array;
+}
+
+gboolean
+gimp_stroke_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_point_at_dist (stroke,
+ dist,
+ precision,
+ position,
+ slope);
+}
+
+
+static gboolean
+gimp_stroke_real_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope)
+{
+ GArray *points;
+ gint i;
+ gdouble length;
+ gdouble segment_length;
+ gboolean ret = FALSE;
+ GimpCoords difference;
+
+ points = gimp_stroke_interpolate (stroke, precision, NULL);
+ if (points == NULL)
+ return ret;
+
+ length = 0;
+ for (i=0; i < points->len - 1; i++)
+ {
+ gimp_coords_difference (&(g_array_index (points, GimpCoords , i)),
+ &(g_array_index (points, GimpCoords , i+1)),
+ &difference);
+ segment_length = gimp_coords_length (&difference);
+
+ if (segment_length == 0 || length + segment_length < dist )
+ {
+ length += segment_length;
+ }
+ else
+ {
+ /* x = x1 + (x2 - x1 ) u */
+ /* x = x1 (1-u) + u x2 */
+
+ gdouble u = (dist - length) / segment_length;
+
+ gimp_coords_mix (1 - u, &(g_array_index (points, GimpCoords , i)),
+ u, &(g_array_index (points, GimpCoords , i+1)),
+ position);
+
+ if (difference.x == 0)
+ *slope = G_MAXDOUBLE;
+ else
+ *slope = difference.y / difference.x;
+
+ ret = TRUE;
+ break;
+ }
+ }
+
+ g_array_free (points, TRUE);
+
+ return ret;
+}
diff --git a/app/vectors/gimpstroke.h b/app/vectors/gimpstroke.h
new file mode 100644
index 0000000..f44b9ab
--- /dev/null
+++ b/app/vectors/gimpstroke.h
@@ -0,0 +1,343 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpstroke.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_STROKE_H__
+#define __GIMP_STROKE_H__
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_STROKE (gimp_stroke_get_type ())
+#define GIMP_STROKE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STROKE, GimpStroke))
+#define GIMP_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STROKE, GimpStrokeClass))
+#define GIMP_IS_STROKE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STROKE))
+#define GIMP_IS_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STROKE))
+#define GIMP_STROKE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STROKE, GimpStrokeClass))
+
+
+typedef struct _GimpStrokeClass GimpStrokeClass;
+
+struct _GimpStroke
+{
+ GimpObject parent_instance;
+ gint ID;
+
+ GQueue *anchors;
+
+ gboolean closed;
+};
+
+struct _GimpStrokeClass
+{
+ GimpObjectClass parent_class;
+
+ void (* changed) (GimpStroke *stroke);
+ void (* removed) (GimpStroke *stroke);
+
+ GimpAnchor * (* anchor_get) (GimpStroke *stroke,
+ const GimpCoords *coord);
+ gdouble (* nearest_point_get) (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+ gdouble (* nearest_tangent_get) (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+ gdouble (* nearest_intersection_get)
+ (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *direction,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+ GimpAnchor * (* anchor_get_next) (GimpStroke *stroke,
+ const GimpAnchor *prev);
+ void (* anchor_select) (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive);
+ void (* anchor_move_relative) (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+ void (* anchor_move_absolute) (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+ void (* anchor_convert) (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+ void (* anchor_delete) (GimpStroke *stroke,
+ GimpAnchor *anchor);
+
+ gboolean (* point_is_movable) (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+ void (* point_move_relative) (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+ void (* point_move_absolute) (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+ void (* close) (GimpStroke *stroke);
+ GimpStroke * (* open) (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+ gboolean (* anchor_is_insertable) (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+ GimpAnchor * (* anchor_insert) (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+ gboolean (* is_extendable) (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+ GimpAnchor * (* extend) (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode);
+ gboolean (* connect_stroke) (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+
+ gboolean (* is_empty) (GimpStroke *stroke);
+ gdouble (* get_length) (GimpStroke *stroke,
+ gdouble precision);
+ gdouble (* get_distance) (GimpStroke *stroke,
+ const GimpCoords *coord);
+ gboolean (* get_point_at_dist) (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope);
+
+ GArray * (* interpolate) (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed);
+
+ GimpStroke * (* duplicate) (GimpStroke *stroke);
+
+ GimpBezierDesc * (* make_bezier) (GimpStroke *stroke);
+
+ void (* translate) (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y);
+ void (* scale) (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y);
+ void (* rotate) (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle);
+ void (* flip) (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis);
+ void (* flip_free) (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+ void (* transform) (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+ GList * (* get_draw_anchors) (GimpStroke *stroke);
+ GList * (* get_draw_controls) (GimpStroke *stroke);
+ GArray * (* get_draw_lines) (GimpStroke *stroke);
+ GArray * (* control_points_get) (GimpStroke *stroke,
+ gboolean *ret_closed);
+};
+
+
+GType gimp_stroke_get_type (void) G_GNUC_CONST;
+
+void gimp_stroke_set_ID (GimpStroke *stroke,
+ gint id);
+gint gimp_stroke_get_ID (GimpStroke *stroke);
+
+
+/* accessing / modifying the anchors */
+
+GArray * gimp_stroke_control_points_get (GimpStroke *stroke,
+ gboolean *closed);
+
+GimpAnchor * gimp_stroke_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord);
+
+gdouble gimp_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+gdouble gimp_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *coords2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+gdouble gimp_stroke_nearest_intersection_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *direction,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+
+
+/* prev == NULL: "first" anchor */
+GimpAnchor * gimp_stroke_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev);
+
+void gimp_stroke_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive);
+
+/* type will be an xorable enum:
+ * VECTORS_NONE, VECTORS_FIX_ANGLE, VECTORS_FIX_RATIO, VECTORS_RESTRICT_ANGLE
+ * or so.
+ */
+void gimp_stroke_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature);
+void gimp_stroke_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+gboolean gimp_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+void gimp_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+void gimp_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+void gimp_stroke_close (GimpStroke *stroke);
+
+void gimp_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+
+void gimp_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor);
+
+GimpStroke * gimp_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+gboolean gimp_stroke_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+GimpAnchor * gimp_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+
+gboolean gimp_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+
+GimpAnchor * gimp_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode);
+
+gboolean gimp_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+
+gboolean gimp_stroke_is_empty (GimpStroke *stroke);
+
+/* accessing the shape of the curve */
+
+gdouble gimp_stroke_get_length (GimpStroke *stroke,
+ gdouble precision);
+gdouble gimp_stroke_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord);
+
+gboolean gimp_stroke_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope);
+
+/* returns an array of valid coordinates */
+GArray * gimp_stroke_interpolate (GimpStroke *stroke,
+ const gdouble precision,
+ gboolean *closed);
+
+GimpStroke * gimp_stroke_duplicate (GimpStroke *stroke);
+
+/* creates a bezier approximation. */
+GimpBezierDesc * gimp_stroke_make_bezier (GimpStroke *stroke);
+
+void gimp_stroke_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y);
+void gimp_stroke_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y);
+void gimp_stroke_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle);
+void gimp_stroke_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis);
+void gimp_stroke_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+void gimp_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+
+GList * gimp_stroke_get_draw_anchors (GimpStroke *stroke);
+GList * gimp_stroke_get_draw_controls (GimpStroke *stroke);
+GArray * gimp_stroke_get_draw_lines (GimpStroke *stroke);
+
+#endif /* __GIMP_STROKE_H__ */
+
diff --git a/app/vectors/gimpvectors-compat.c b/app/vectors/gimpvectors-compat.c
new file mode 100644
index 0000000..5416d00
--- /dev/null
+++ b/app/vectors/gimpvectors-compat.c
@@ -0,0 +1,285 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors-compat.c
+ * Copyright (C) 2003 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpanchor.h"
+#include "gimpbezierstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-compat.h"
+
+
+enum
+{
+ GIMP_VECTORS_COMPAT_ANCHOR = 1,
+ GIMP_VECTORS_COMPAT_CONTROL = 2,
+ GIMP_VECTORS_COMPAT_NEW_STROKE = 3
+};
+
+
+static const GimpCoords default_coords = GIMP_COORDS_DEFAULT_VALUES;
+
+
+GimpVectors *
+gimp_vectors_compat_new (GimpImage *image,
+ const gchar *name,
+ GimpVectorsCompatPoint *points,
+ gint n_points,
+ gboolean closed)
+{
+ GimpVectors *vectors;
+ GimpStroke *stroke;
+ GimpCoords *coords;
+ GimpCoords *curr_stroke;
+ GimpCoords *curr_coord;
+ gint i;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+ g_return_val_if_fail (points != NULL || n_points == 0, NULL);
+ g_return_val_if_fail (n_points >= 0, NULL);
+
+ vectors = gimp_vectors_new (image, name);
+
+ coords = g_new0 (GimpCoords, n_points + 1);
+
+ curr_stroke = curr_coord = coords;
+
+ /* skip the first control point, will set it later */
+ curr_coord++;
+
+ for (i = 0; i < n_points; i++)
+ {
+ *curr_coord = default_coords;
+
+ curr_coord->x = points[i].x;
+ curr_coord->y = points[i].y;
+
+ /* copy the first anchor to be the first control point */
+ if (curr_coord == curr_stroke + 1)
+ *curr_stroke = *curr_coord;
+
+ /* found new stroke start */
+ if (points[i].type == GIMP_VECTORS_COMPAT_NEW_STROKE)
+ {
+ /* copy the last control point to the beginning of the stroke */
+ *curr_stroke = *(curr_coord - 1);
+
+ stroke =
+ gimp_bezier_stroke_new_from_coords (curr_stroke,
+ curr_coord - curr_stroke - 1,
+ TRUE);
+ gimp_vectors_stroke_add (vectors, stroke);
+ g_object_unref (stroke);
+
+ /* start a new stroke */
+ curr_stroke = curr_coord - 1;
+
+ /* copy the first anchor to be the first control point */
+ *curr_stroke = *curr_coord;
+ }
+
+ curr_coord++;
+ }
+
+ if (closed)
+ {
+ /* copy the last control point to the beginning of the stroke */
+ curr_coord--;
+ *curr_stroke = *curr_coord;
+ }
+
+ stroke = gimp_bezier_stroke_new_from_coords (curr_stroke,
+ curr_coord - curr_stroke,
+ closed);
+ gimp_vectors_stroke_add (vectors, stroke);
+ g_object_unref (stroke);
+
+ g_free (coords);
+
+ return vectors;
+}
+
+gboolean
+gimp_vectors_compat_is_compatible (GimpImage *image)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpVectors *vectors = GIMP_VECTORS (list->data);
+ GList *strokes;
+ gint open_count = 0;
+
+ if (gimp_item_get_visible (GIMP_ITEM (vectors)))
+ return FALSE;
+
+ for (strokes = vectors->strokes->head;
+ strokes;
+ strokes = g_list_next (strokes))
+ {
+ GimpStroke *stroke = GIMP_STROKE (strokes->data);
+
+ if (! GIMP_IS_BEZIER_STROKE (stroke))
+ return FALSE;
+
+ if (!stroke->closed)
+ open_count++;
+ }
+
+ if (open_count >= 2)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+GimpVectorsCompatPoint *
+gimp_vectors_compat_get_points (GimpVectors *vectors,
+ gint32 *n_points,
+ gint32 *closed)
+{
+ GimpVectorsCompatPoint *points;
+ GList *strokes;
+ gint i;
+ GList *postponed = NULL; /* for the one open stroke... */
+ gint open_count;
+ gboolean first_stroke = TRUE;
+
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+ g_return_val_if_fail (n_points != NULL, NULL);
+ g_return_val_if_fail (closed != NULL, NULL);
+
+ *n_points = 0;
+ *closed = TRUE;
+
+ open_count = 0;
+
+ for (strokes = vectors->strokes->head;
+ strokes;
+ strokes = g_list_next (strokes))
+ {
+ GimpStroke *stroke = strokes->data;
+ gint n_anchors;
+
+ if (! stroke->closed)
+ {
+ open_count++;
+ postponed = strokes;
+ *closed = FALSE;
+
+ if (open_count >= 2)
+ {
+ g_warning ("gimp_vectors_compat_get_points(): convert failed");
+ *n_points = 0;
+ return NULL;
+ }
+ }
+
+ n_anchors = g_queue_get_length (stroke->anchors);
+
+ if (! stroke->closed)
+ n_anchors--;
+
+ *n_points += n_anchors;
+ }
+
+ points = g_new0 (GimpVectorsCompatPoint, *n_points);
+
+ i = 0;
+
+ for (strokes = vectors->strokes->head;
+ strokes || postponed;
+ strokes = g_list_next (strokes))
+ {
+ GimpStroke *stroke;
+ GList *anchors;
+
+ if (strokes)
+ {
+ if (postponed && strokes == postponed)
+ /* we need to visit the open stroke last... */
+ continue;
+ else
+ stroke = GIMP_STROKE (strokes->data);
+ }
+ else
+ {
+ stroke = GIMP_STROKE (postponed->data);
+ postponed = NULL;
+ }
+
+ for (anchors = stroke->anchors->head;
+ anchors;
+ anchors = g_list_next (anchors))
+ {
+ GimpAnchor *anchor = anchors->data;
+
+ /* skip the first anchor, will add it at the end if needed */
+ if (! anchors->prev)
+ continue;
+
+ switch (anchor->type)
+ {
+ case GIMP_ANCHOR_ANCHOR:
+ if (anchors->prev == stroke->anchors->head && ! first_stroke)
+ points[i].type = GIMP_VECTORS_COMPAT_NEW_STROKE;
+ else
+ points[i].type = GIMP_VECTORS_COMPAT_ANCHOR;
+ break;
+
+ case GIMP_ANCHOR_CONTROL:
+ points[i].type = GIMP_VECTORS_COMPAT_CONTROL;
+ break;
+ }
+
+ points[i].x = anchor->position.x;
+ points[i].y = anchor->position.y;
+
+ i++;
+
+ /* write the skipped control point */
+ if (! anchors->next && stroke->closed)
+ {
+ anchor = g_queue_peek_head (stroke->anchors);
+
+ points[i].type = GIMP_VECTORS_COMPAT_CONTROL;
+ points[i].x = anchor->position.x;
+ points[i].y = anchor->position.y;
+
+ i++;
+ }
+ }
+ first_stroke = FALSE;
+ }
+
+ return points;
+}
diff --git a/app/vectors/gimpvectors-compat.h b/app/vectors/gimpvectors-compat.h
new file mode 100644
index 0000000..561043a
--- /dev/null
+++ b/app/vectors/gimpvectors-compat.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors-compat.h
+ * Copyright (C) 2003 Michael Natterer <mitch@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_COMPAT_H__
+#define __GIMP_VECTORS_COMPAT_H__
+
+
+typedef struct _GimpVectorsCompatPoint GimpVectorsCompatPoint;
+
+struct _GimpVectorsCompatPoint
+{
+ guint32 type;
+ gdouble x;
+ gdouble y;
+};
+
+
+GimpVectors * gimp_vectors_compat_new (GimpImage *image,
+ const gchar *name,
+ GimpVectorsCompatPoint *points,
+ gint n_points,
+ gboolean closed);
+
+gboolean gimp_vectors_compat_is_compatible (GimpImage *image);
+
+GimpVectorsCompatPoint * gimp_vectors_compat_get_points (GimpVectors *vectors,
+ gint32 *n_points,
+ gint32 *closed);
+
+
+#endif /* __GIMP_VECTORS_COMPAT_H__ */
diff --git a/app/vectors/gimpvectors-export.c b/app/vectors/gimpvectors-export.c
new file mode 100644
index 0000000..f8a33e8
--- /dev/null
+++ b/app/vectors/gimpvectors-export.c
@@ -0,0 +1,333 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "vectors-types.h"
+
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+
+#include "gimpanchor.h"
+#include "gimpstroke.h"
+#include "gimpbezierstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-export.h"
+
+#include "gimp-intl.h"
+
+
+static GString * gimp_vectors_export (GimpImage *image,
+ GimpVectors *vectors);
+static void gimp_vectors_export_image_size (GimpImage *image,
+ GString *str);
+static void gimp_vectors_export_path (GimpVectors *vectors,
+ GString *str);
+static gchar * gimp_vectors_export_path_data (GimpVectors *vectors);
+
+
+/**
+ * gimp_vectors_export_file:
+ * @image: the #GimpImage from which to export vectors
+ * @vectors: a #GimpVectors object or %NULL to export all vectors in @image
+ * @file: the file to write
+ * @error: return location for errors
+ *
+ * Exports one or more vectors to a SVG file.
+ *
+ * Return value: %TRUE on success,
+ * %FALSE if there was an error writing the file
+ **/
+gboolean
+gimp_vectors_export_file (GimpImage *image,
+ GimpVectors *vectors,
+ GFile *file,
+ GError **error)
+{
+ GOutputStream *output;
+ GString *string;
+ GError *my_error = NULL;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, error));
+ if (! output)
+ return FALSE;
+
+ string = gimp_vectors_export (image, vectors);
+
+ if (! g_output_stream_write_all (output, string->str, string->len,
+ NULL, NULL, &my_error))
+ {
+ GCancellable *cancellable = g_cancellable_new ();
+
+ g_set_error (error, my_error->domain, my_error->code,
+ _("Writing SVG file '%s' failed: %s"),
+ gimp_file_get_utf8_name (file), my_error->message);
+ g_clear_error (&my_error);
+ g_string_free (string, TRUE);
+
+ /* Cancel the overwrite initiated by g_file_replace(). */
+ g_cancellable_cancel (cancellable);
+ g_output_stream_close (output, cancellable, NULL);
+ g_object_unref (cancellable);
+ g_object_unref (output);
+
+ return FALSE;
+ }
+
+ g_string_free (string, TRUE);
+ g_object_unref (output);
+
+ return TRUE;
+}
+
+/**
+ * gimp_vectors_export_string:
+ * @image: the #GimpImage from which to export vectors
+ * @vectors: a #GimpVectors object or %NULL to export all vectors in @image
+ *
+ * Exports one or more vectors to a SVG string.
+ *
+ * Return value: a %NUL-terminated string that holds a complete XML document
+ **/
+gchar *
+gimp_vectors_export_string (GimpImage *image,
+ GimpVectors *vectors)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), NULL);
+
+ return g_string_free (gimp_vectors_export (image, vectors), FALSE);
+}
+
+static GString *
+gimp_vectors_export (GimpImage *image,
+ GimpVectors *vectors)
+{
+ GString *str = g_string_new (NULL);
+
+ g_string_append_printf (str,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n"
+ " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
+ "\n"
+ "<svg xmlns=\"http://www.w3.org/2000/svg\"\n");
+
+ g_string_append (str, " ");
+ gimp_vectors_export_image_size (image, str);
+ g_string_append_c (str, '\n');
+
+ g_string_append_printf (str,
+ " viewBox=\"0 0 %d %d\">\n",
+ gimp_image_get_width (image),
+ gimp_image_get_height (image));
+
+ if (vectors)
+ {
+ gimp_vectors_export_path (vectors, str);
+ }
+ else
+ {
+ GList *list;
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = list->next)
+ {
+ gimp_vectors_export_path (GIMP_VECTORS (list->data), str);
+ }
+ }
+
+ g_string_append (str, "</svg>\n");
+
+ return str;
+}
+
+static void
+gimp_vectors_export_image_size (GimpImage *image,
+ GString *str)
+{
+ GimpUnit unit;
+ const gchar *abbrev;
+ gchar wbuf[G_ASCII_DTOSTR_BUF_SIZE];
+ gchar hbuf[G_ASCII_DTOSTR_BUF_SIZE];
+ gdouble xres;
+ gdouble yres;
+ gdouble w, h;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ w = (gdouble) gimp_image_get_width (image) / xres;
+ h = (gdouble) gimp_image_get_height (image) / yres;
+
+ /* FIXME: should probably use the display unit here */
+ unit = gimp_image_get_unit (image);
+ switch (unit)
+ {
+ case GIMP_UNIT_INCH: abbrev = "in"; break;
+ case GIMP_UNIT_MM: abbrev = "mm"; break;
+ case GIMP_UNIT_POINT: abbrev = "pt"; break;
+ case GIMP_UNIT_PICA: abbrev = "pc"; break;
+ default: abbrev = "cm";
+ unit = GIMP_UNIT_MM;
+ w /= 10.0;
+ h /= 10.0;
+ break;
+ }
+
+ g_ascii_formatd (wbuf, sizeof (wbuf), "%g", w * gimp_unit_get_factor (unit));
+ g_ascii_formatd (hbuf, sizeof (hbuf), "%g", h * gimp_unit_get_factor (unit));
+
+ g_string_append_printf (str,
+ "width=\"%s%s\" height=\"%s%s\"",
+ wbuf, abbrev, hbuf, abbrev);
+}
+
+static void
+gimp_vectors_export_path (GimpVectors *vectors,
+ GString *str)
+{
+ const gchar *name = gimp_object_get_name (vectors);
+ gchar *data = gimp_vectors_export_path_data (vectors);
+ gchar *esc_name;
+
+ esc_name = g_markup_escape_text (name, strlen (name));
+
+ g_string_append_printf (str,
+ " <path id=\"%s\"\n"
+ " fill=\"none\" stroke=\"black\" stroke-width=\"1\"\n"
+ " d=\"%s\" />\n",
+ esc_name, data);
+
+ g_free (esc_name);
+ g_free (data);
+}
+
+
+#define NEWLINE "\n "
+
+static gchar *
+gimp_vectors_export_path_data (GimpVectors *vectors)
+{
+ GString *str;
+ GList *strokes;
+ gchar x_string[G_ASCII_DTOSTR_BUF_SIZE];
+ gchar y_string[G_ASCII_DTOSTR_BUF_SIZE];
+ gboolean closed = FALSE;
+
+ str = g_string_new (NULL);
+
+ for (strokes = vectors->strokes->head;
+ strokes;
+ strokes = strokes->next)
+ {
+ GimpStroke *stroke = strokes->data;
+ GArray *control_points;
+ GimpAnchor *anchor;
+ gint i;
+
+ if (closed)
+ g_string_append_printf (str, NEWLINE);
+
+ control_points = gimp_stroke_control_points_get (stroke, &closed);
+
+ if (GIMP_IS_BEZIER_STROKE (stroke))
+ {
+ if (control_points->len >= 3)
+ {
+ anchor = &g_array_index (control_points, GimpAnchor, 1);
+ g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.x);
+ g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.y);
+ g_string_append_printf (str, "M %s,%s", x_string, y_string);
+ }
+
+ if (control_points->len > 3)
+ {
+ g_string_append_printf (str, NEWLINE "C");
+ }
+
+ for (i = 2; i < (control_points->len + (closed ? 2 : - 1)); i++)
+ {
+ if (i > 2 && i % 3 == 2)
+ g_string_append_printf (str, NEWLINE " ");
+
+ anchor = &g_array_index (control_points, GimpAnchor,
+ i % control_points->len);
+ g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.x);
+ g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.y);
+ g_string_append_printf (str, " %s,%s", x_string, y_string);
+ }
+
+ if (closed && control_points->len > 3)
+ g_string_append_printf (str, " Z");
+ }
+ else
+ {
+ g_printerr ("Unknown stroke type\n");
+
+ if (control_points->len >= 1)
+ {
+ anchor = &g_array_index (control_points, GimpAnchor, 0);
+ g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
+ ".2f", anchor->position.x);
+ g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
+ ".2f", anchor->position.y);
+ g_string_append_printf (str, "M %s,%s", x_string, y_string);
+ }
+
+ if (control_points->len > 1)
+ {
+ g_string_append_printf (str, NEWLINE "L");
+ }
+
+ for (i = 1; i < control_points->len; i++)
+ {
+ if (i > 1 && i % 3 == 1)
+ g_string_append_printf (str, NEWLINE " ");
+
+ anchor = &g_array_index (control_points, GimpAnchor, i);
+ g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.x);
+ g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.2f", anchor->position.y);
+ g_string_append_printf (str, " %s,%s", x_string, y_string);
+ }
+
+ if (closed && control_points->len > 1)
+ g_string_append_printf (str, " Z");
+ }
+
+ g_array_free (control_points, TRUE);
+ }
+
+ return g_strchomp (g_string_free (str, FALSE));
+}
diff --git a/app/vectors/gimpvectors-export.h b/app/vectors/gimpvectors-export.h
new file mode 100644
index 0000000..d61eae2
--- /dev/null
+++ b/app/vectors/gimpvectors-export.h
@@ -0,0 +1,30 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_EXPORT_H__
+#define __GIMP_VECTORS_EXPORT_H__
+
+
+gboolean gimp_vectors_export_file (GimpImage *image,
+ GimpVectors *vectors,
+ GFile *file,
+ GError **error);
+gchar * gimp_vectors_export_string (GimpImage *image,
+ GimpVectors *vectors);
+
+
+#endif /* __GIMP_VECTORS_IMPORT_H__ */
diff --git a/app/vectors/gimpvectors-import.c b/app/vectors/gimpvectors-import.c
new file mode 100644
index 0000000..fa631ee
--- /dev/null
+++ b/app/vectors/gimpvectors-import.c
@@ -0,0 +1,1784 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpVectors Import
+ * Copyright (C) 2003-2004 Sven Neumann <sven@gimp.org>
+ *
+ * Some code here is based on code from librsvg that was originally
+ * written by Raph Levien <raph@artofcode.com> for Gill.
+ *
+ * This SVG path importer implements a subset of SVG that is
+ * sufficient to parse path elements and basic shapes and to apply
+ * transformations as described by the SVG specification:
+ * http://www.w3.org/TR/SVG/. It must handle the SVG files exported
+ * by GIMP but it is also supposed to be able to extract paths and
+ * shapes from foreign SVG documents.
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "config/gimpxmlparser.h"
+
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-undo.h"
+
+#include "gimpbezierstroke.h"
+#include "gimpstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-import.h"
+
+#include "gimp-intl.h"
+
+
+#define COORDS_INIT \
+ { \
+ .x = 0.0, \
+ .y = 0.0, \
+ .pressure = 1.0, \
+ .xtilt = 0.0, \
+ .ytilt = 0.0, \
+ .wheel = 0.5, \
+ .velocity = 0.0, \
+ .direction = 0.0 \
+ }
+
+
+typedef struct
+{
+ GQueue *stack;
+ GimpImage *image;
+ gboolean scale;
+ gint svg_depth;
+} SvgParser;
+
+
+typedef struct _SvgHandler SvgHandler;
+
+struct _SvgHandler
+{
+ const gchar *name;
+
+ void (* start) (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+ void (* end) (SvgHandler *handler,
+ SvgParser *parser);
+
+ gdouble width;
+ gdouble height;
+ gchar *id;
+ GList *paths;
+ GimpMatrix3 *transform;
+};
+
+
+typedef struct
+{
+ gchar *id;
+ GList *strokes;
+} SvgPath;
+
+
+static gboolean gimp_vectors_import (GimpImage *image,
+ GFile *file,
+ const gchar *str,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error);
+
+static void svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+
+static const GMarkupParser markup_parser =
+{
+ svg_parser_start_element,
+ svg_parser_end_element,
+ NULL, /* characters */
+ NULL, /* passthrough */
+ NULL /* error */
+};
+
+
+static void svg_handler_svg_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_svg_end (SvgHandler *handler,
+ SvgParser *parser);
+static void svg_handler_group_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_path_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_rect_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_ellipse_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_line_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+static void svg_handler_poly_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser);
+
+static const SvgHandler svg_handlers[] =
+{
+ { "svg", svg_handler_svg_start, svg_handler_svg_end },
+ { "g", svg_handler_group_start, NULL },
+ { "path", svg_handler_path_start, NULL },
+ { "rect", svg_handler_rect_start, NULL },
+ { "circle", svg_handler_ellipse_start, NULL },
+ { "ellipse", svg_handler_ellipse_start, NULL },
+ { "line", svg_handler_line_start, NULL },
+ { "polyline", svg_handler_poly_start, NULL },
+ { "polygon", svg_handler_poly_start, NULL }
+};
+
+
+static gboolean parse_svg_length (const gchar *value,
+ gdouble reference,
+ gdouble resolution,
+ gdouble *length);
+static gboolean parse_svg_viewbox (const gchar *value,
+ gdouble *width,
+ gdouble *height,
+ GimpMatrix3 *matrix);
+static gboolean parse_svg_transform (const gchar *value,
+ GimpMatrix3 *matrix);
+static GList * parse_path_data (const gchar *data);
+
+
+/**
+ * gimp_vectors_import_file:
+ * @image: the #GimpImage to add the paths to
+ * @file: a SVG file
+ * @merge: should multiple paths be merged into a single #GimpVectors object
+ * @scale: should the SVG be scaled to fit the image dimensions
+ * @position: position in the image's vectors stack where to add the vectors
+ * @error: location to store possible errors
+ *
+ * Imports one or more paths and basic shapes from a SVG file.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+gimp_vectors_import_file (GimpImage *image,
+ GFile *file,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (G_IS_FILE (file), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ GIMP_IS_VECTORS (parent), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_is_attached (GIMP_ITEM (parent)), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_image (GIMP_ITEM (parent)) == image,
+ FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)),
+ FALSE);
+ g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_vectors_import (image, file, NULL, 0, merge, scale,
+ parent, position,
+ ret_vectors, error);
+}
+
+/**
+ * gimp_vectors_import_string:
+ * @image: the #GimpImage to add the paths to
+ * @buffer: a character buffer to parse
+ * @len: number of bytes in @str or -1 if @str is %NUL-terminated
+ * @merge: should multiple paths be merged into a single #GimpVectors object
+ * @scale: should the SVG be scaled to fit the image dimensions
+ * @error: location to store possible errors
+ *
+ * Imports one or more paths and basic shapes from a SVG file.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred
+ **/
+gboolean
+gimp_vectors_import_buffer (GimpImage *image,
+ const gchar *buffer,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (buffer != NULL || len == 0, FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ GIMP_IS_VECTORS (parent), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_is_attached (GIMP_ITEM (parent)), FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_item_get_image (GIMP_ITEM (parent)) == image,
+ FALSE);
+ g_return_val_if_fail (parent == NULL ||
+ parent == GIMP_IMAGE_ACTIVE_PARENT ||
+ gimp_viewable_get_children (GIMP_VIEWABLE (parent)),
+ FALSE);
+ g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ return gimp_vectors_import (image, NULL, buffer, len, merge, scale,
+ parent, position,
+ ret_vectors, error);
+}
+
+static gboolean
+gimp_vectors_import (GimpImage *image,
+ GFile *file,
+ const gchar *str,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error)
+{
+ GimpXmlParser *xml_parser;
+ SvgParser parser;
+ GList *paths;
+ SvgHandler *base;
+ gboolean success = TRUE;
+
+ parser.stack = g_queue_new ();
+ parser.image = image;
+ parser.scale = scale;
+ parser.svg_depth = 0;
+
+ /* the base of the stack, defines the size of the view-port */
+ base = g_slice_new0 (SvgHandler);
+ base->name = "image";
+ base->width = gimp_image_get_width (image);
+ base->height = gimp_image_get_height (image);
+
+ g_queue_push_head (parser.stack, base);
+
+ xml_parser = gimp_xml_parser_new (&markup_parser, &parser);
+
+ if (file)
+ success = gimp_xml_parser_parse_gfile (xml_parser, file, error);
+ else
+ success = gimp_xml_parser_parse_buffer (xml_parser, str, len, error);
+
+ gimp_xml_parser_free (xml_parser);
+
+ if (success)
+ {
+ if (base->paths)
+ {
+ GimpVectors *vectors = NULL;
+
+ base->paths = g_list_reverse (base->paths);
+
+ merge = merge && base->paths->next;
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_VECTORS_IMPORT,
+ _("Import Paths"));
+
+ for (paths = base->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ if (! merge || ! vectors)
+ {
+ vectors = gimp_vectors_new (image,
+ ((merge || ! path->id) ?
+ _("Imported Path") : path->id));
+ gimp_image_add_vectors (image, vectors,
+ parent, position, TRUE);
+ gimp_vectors_freeze (vectors);
+
+ if (ret_vectors)
+ *ret_vectors = g_list_prepend (*ret_vectors, vectors);
+
+ if (position != -1)
+ position++;
+ }
+
+ for (list = path->strokes; list; list = list->next)
+ gimp_vectors_stroke_add (vectors, GIMP_STROKE (list->data));
+
+ if (! merge)
+ gimp_vectors_thaw (vectors);
+
+ g_list_free_full (path->strokes, g_object_unref);
+ path->strokes = NULL;
+ }
+
+ if (merge)
+ gimp_vectors_thaw (vectors);
+
+ gimp_image_undo_group_end (image);
+ }
+ else
+ {
+ if (file)
+ g_set_error (error, GIMP_ERROR, GIMP_FAILED,
+ _("No paths found in '%s'"),
+ gimp_file_get_utf8_name (file));
+ else
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No paths found in the buffer"));
+
+ success = FALSE;
+ }
+ }
+ else if (error && *error && file) /* parser reported an error */
+ {
+ gchar *msg = (*error)->message;
+
+ (*error)->message =
+ g_strdup_printf (_("Failed to import paths from '%s': %s"),
+ gimp_file_get_utf8_name (file), msg);
+
+ g_free (msg);
+ }
+
+ while ((base = g_queue_pop_head (parser.stack)) != NULL)
+ {
+ for (paths = base->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ g_free (path->id);
+
+ for (list = path->strokes; list; list = list->next)
+ g_object_unref (list->data);
+
+ g_list_free (path->strokes);
+
+ g_slice_free (SvgPath, path);
+ }
+
+ g_list_free (base->paths);
+
+ g_slice_free (GimpMatrix3, base->transform);
+ g_slice_free (SvgHandler, base);
+ }
+
+ g_queue_free (parser.stack);
+
+ return success;
+}
+
+static void
+svg_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+ SvgHandler *handler;
+ SvgHandler *base;
+ gint i = 0;
+
+ handler = g_slice_new0 (SvgHandler);
+ base = g_queue_peek_head (parser->stack);
+
+ /* if the element is not rendered, always use the generic handler */
+ if (base->width <= 0.0 || base->height <= 0.0)
+ i = G_N_ELEMENTS (svg_handlers);
+
+ for (; i < G_N_ELEMENTS (svg_handlers); i++)
+ if (strcmp (svg_handlers[i].name, element_name) == 0)
+ {
+ handler->name = svg_handlers[i].name;
+ handler->start = svg_handlers[i].start;
+ break;
+ }
+
+ handler->width = base->width;
+ handler->height = base->height;
+
+ g_queue_push_head (parser->stack, handler);
+
+ if (handler->start)
+ handler->start (handler, attribute_names, attribute_values, parser);
+}
+
+static void
+svg_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ SvgParser *parser = user_data;
+ SvgHandler *handler;
+ SvgHandler *base;
+ GList *paths;
+
+ handler = g_queue_pop_head (parser->stack);
+
+ g_return_if_fail (handler != NULL &&
+ (handler->name == NULL ||
+ strcmp (handler->name, element_name) == 0));
+
+ if (handler->end)
+ handler->end (handler, parser);
+
+ if (handler->paths)
+ {
+ if (handler->transform)
+ {
+ for (paths = handler->paths; paths; paths = paths->next)
+ {
+ SvgPath *path = paths->data;
+ GList *list;
+
+ for (list = path->strokes; list; list = list->next)
+ gimp_stroke_transform (GIMP_STROKE (list->data),
+ handler->transform, NULL);
+ }
+
+ g_slice_free (GimpMatrix3, handler->transform);
+ }
+
+ base = g_queue_peek_head (parser->stack);
+ base->paths = g_list_concat (base->paths, handler->paths);
+ }
+
+ g_slice_free (SvgHandler, handler);
+}
+
+static void
+svg_handler_svg_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ GimpMatrix3 *matrix;
+ GimpMatrix3 box;
+ const gchar *viewbox = NULL;
+ gdouble x = 0;
+ gdouble y = 0;
+ gdouble w = handler->width;
+ gdouble h = handler->height;
+ gdouble xres;
+ gdouble yres;
+
+ matrix = g_slice_new (GimpMatrix3);
+ gimp_matrix3_identity (matrix);
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'x':
+ if (strcmp (*names, "x") == 0)
+ parse_svg_length (*values, handler->width, xres, &x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y") == 0)
+ parse_svg_length (*values, handler->height, yres, &y);
+ break;
+
+ case 'w':
+ if (strcmp (*names, "width") == 0)
+ parse_svg_length (*values, handler->width, xres, &w);
+ break;
+
+ case 'h':
+ if (strcmp (*names, "height") == 0)
+ parse_svg_length (*values, handler->height, yres, &h);
+ break;
+
+ case 'v':
+ if (strcmp (*names, "viewBox") == 0)
+ viewbox = *values;
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (x || y)
+ {
+ /* according to the spec offsets are meaningless on the outermost svg */
+ if (parser->svg_depth > 0)
+ gimp_matrix3_translate (matrix, x, y);
+ }
+
+ if (viewbox && parse_svg_viewbox (viewbox, &w, &h, &box))
+ {
+ gimp_matrix3_mult (&box, matrix);
+ }
+
+ /* optionally scale the outermost svg to image size */
+ if (parser->scale && parser->svg_depth == 0)
+ {
+ if (w > 0.0 && h > 0.0)
+ gimp_matrix3_scale (matrix,
+ gimp_image_get_width (parser->image) / w,
+ gimp_image_get_height (parser->image) / h);
+ }
+
+ handler->width = w;
+ handler->height = h;
+
+ handler->transform = matrix;
+
+ parser->svg_depth++;
+}
+
+static void
+svg_handler_svg_end (SvgHandler *handler,
+ SvgParser *parser)
+{
+ parser->svg_depth--;
+}
+
+static void
+svg_handler_group_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ while (*names)
+ {
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+
+#ifdef DEBUG_VECTORS_IMPORT
+ g_printerr ("transform %s: %g %g %g %g %g %g %g %g %g\n",
+ handler->id ? handler->id : "(null)",
+ handler->transform->coeff[0][0],
+ handler->transform->coeff[0][1],
+ handler->transform->coeff[0][2],
+ handler->transform->coeff[1][0],
+ handler->transform->coeff[1][1],
+ handler->transform->coeff[1][2],
+ handler->transform->coeff[2][0],
+ handler->transform->coeff[2][1],
+ handler->transform->coeff[2][2]);
+#endif
+ }
+ }
+
+ names++;
+ values++;
+ }
+}
+
+static void
+svg_handler_path_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'd':
+ if (strcmp (*names, "d") == 0 && ! path->strokes)
+ path->strokes = parse_path_data (*values);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_rect_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ gdouble x = 0.0;
+ gdouble y = 0.0;
+ gdouble width = 0.0;
+ gdouble height = 0.0;
+ gdouble rx = 0.0;
+ gdouble ry = 0.0;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'x':
+ if (strcmp (*names, "x") == 0)
+ parse_svg_length (*values, handler->width, xres, &x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y") == 0)
+ parse_svg_length (*values, handler->height, yres, &y);
+ break;
+
+ case 'w':
+ if (strcmp (*names, "width") == 0)
+ parse_svg_length (*values, handler->width, xres, &width);
+ break;
+
+ case 'h':
+ if (strcmp (*names, "height") == 0)
+ parse_svg_length (*values, handler->height, yres, &height);
+ break;
+
+ case 'r':
+ if (strcmp (*names, "rx") == 0)
+ parse_svg_length (*values, handler->width, xres, &rx);
+ else if (strcmp (*names, "ry") == 0)
+ parse_svg_length (*values, handler->height, yres, &ry);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (width > 0.0 && height > 0.0 && rx >= 0.0 && ry >= 0.0)
+ {
+ GimpStroke *stroke;
+ GimpCoords point = COORDS_INIT;
+
+ if (rx == 0.0)
+ rx = ry;
+ if (ry == 0.0)
+ ry = rx;
+
+ rx = MIN (rx, width / 2);
+ ry = MIN (ry, height / 2);
+
+ point.x = x + width - rx;
+ point.y = y;
+ stroke = gimp_bezier_stroke_new_moveto (&point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + width;
+ end.y = y + ry;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x + width;
+ point.y = y + height - ry;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + width - rx;
+ end.y = y + height;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x + rx;
+ point.y = y + height;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x;
+ end.y = y + height - ry;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ point.x = x;
+ point.y = y + ry;
+ gimp_bezier_stroke_lineto (stroke, &point);
+
+ if (rx)
+ {
+ GimpCoords end = COORDS_INIT;
+
+ end.x = x + rx;
+ end.y = y;
+
+ gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end);
+ }
+
+ /* the last line is handled by closing the stroke */
+ gimp_stroke_close (stroke);
+
+ path->strokes = g_list_prepend (path->strokes, stroke);
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_ellipse_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GimpCoords center = COORDS_INIT;
+ gdouble rx = 0.0;
+ gdouble ry = 0.0;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'c':
+ if (strcmp (*names, "cx") == 0)
+ parse_svg_length (*values, handler->width, xres, &center.x);
+ else if (strcmp (*names, "cy") == 0)
+ parse_svg_length (*values, handler->height, yres, &center.y);
+ break;
+
+ case 'r':
+ if (strcmp (*names, "r") == 0)
+ {
+ parse_svg_length (*values, handler->width, xres, &rx);
+ parse_svg_length (*values, handler->height, yres, &ry);
+ }
+ else if (strcmp (*names, "rx") == 0)
+ {
+ parse_svg_length (*values, handler->width, xres, &rx);
+ }
+ else if (strcmp (*names, "ry") == 0)
+ {
+ parse_svg_length (*values, handler->height, yres, &ry);
+ }
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (rx >= 0.0 && ry >= 0.0)
+ path->strokes = g_list_prepend (path->strokes,
+ gimp_bezier_stroke_new_ellipse (&center,
+ rx, ry,
+ 0.0));
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_line_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GimpCoords start = COORDS_INIT;
+ GimpCoords end = COORDS_INIT;
+ GimpStroke *stroke;
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (parser->image, &xres, &yres);
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'x':
+ if (strcmp (*names, "x1") == 0)
+ parse_svg_length (*values, handler->width, xres, &start.x);
+ else if (strcmp (*names, "x2") == 0)
+ parse_svg_length (*values, handler->width, xres, &end.x);
+ break;
+
+ case 'y':
+ if (strcmp (*names, "y1") == 0)
+ parse_svg_length (*values, handler->height, yres, &start.y);
+ else if (strcmp (*names, "y2") == 0)
+ parse_svg_length (*values, handler->height, yres, &end.y);
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ stroke = gimp_bezier_stroke_new_moveto (&start);
+ gimp_bezier_stroke_lineto (stroke, &end);
+
+ path->strokes = g_list_prepend (path->strokes, stroke);
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static void
+svg_handler_poly_start (SvgHandler *handler,
+ const gchar **names,
+ const gchar **values,
+ SvgParser *parser)
+{
+ SvgPath *path = g_slice_new0 (SvgPath);
+ GString *points = NULL;
+
+ while (*names)
+ {
+ switch (*names[0])
+ {
+ case 'i':
+ if (strcmp (*names, "id") == 0 && ! path->id)
+ path->id = g_strdup (*values);
+ break;
+
+ case 'p':
+ if (strcmp (*names, "points") == 0 && ! points)
+ {
+ const gchar *p = *values;
+ const gchar *m = NULL;
+ const gchar *l = NULL;
+ gint n = 0;
+
+ while (*p)
+ {
+ while (g_ascii_isspace (*p) || *p == ',')
+ p++;
+
+ switch (n)
+ {
+ case 0:
+ m = p;
+ break;
+ case 2:
+ l = p;
+ break;
+ }
+
+ if (*p)
+ n++;
+
+ while (*p && ! g_ascii_isspace (*p) && *p != ',')
+ p++;
+ }
+
+ if ((n > 3) && (n % 2 == 0))
+ {
+ points = g_string_sized_new (p - *values + 8);
+
+ g_string_append_len (points, "M ", 2);
+ g_string_append_len (points, m, l - m);
+
+ g_string_append_len (points, "L ", 2);
+ g_string_append_len (points, l, p - l);
+
+ if (strcmp (handler->name, "polygon") == 0)
+ g_string_append_c (points, 'Z');
+ }
+ }
+ break;
+
+ case 't':
+ if (strcmp (*names, "transform") == 0 && ! handler->transform)
+ {
+ GimpMatrix3 matrix;
+
+ if (parse_svg_transform (*values, &matrix))
+ {
+ handler->transform = g_slice_dup (GimpMatrix3, &matrix);
+ }
+ }
+ break;
+ }
+
+ names++;
+ values++;
+ }
+
+ if (points)
+ {
+ path->strokes = parse_path_data (points->str);
+ g_string_free (points, TRUE);
+ }
+
+ handler->paths = g_list_prepend (handler->paths, path);
+}
+
+static gboolean
+parse_svg_length (const gchar *value,
+ gdouble reference,
+ gdouble resolution,
+ gdouble *length)
+{
+ GimpUnit unit = GIMP_UNIT_PIXEL;
+ gdouble len;
+ gchar *ptr;
+
+ len = g_ascii_strtod (value, &ptr);
+
+ while (g_ascii_isspace (*ptr))
+ ptr++;
+
+ switch (ptr[0])
+ {
+ case '\0':
+ break;
+
+ case 'p':
+ switch (ptr[1])
+ {
+ case 'x': break;
+ case 't': unit = GIMP_UNIT_POINT; break;
+ case 'c': unit = GIMP_UNIT_PICA; break;
+ default:
+ return FALSE;
+ }
+ ptr += 2;
+ break;
+
+ case 'c':
+ if (ptr[1] == 'm')
+ len *= 10.0, unit = GIMP_UNIT_MM;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case 'm':
+ if (ptr[1] == 'm')
+ unit = GIMP_UNIT_MM;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case 'i':
+ if (ptr[1] == 'n')
+ unit = GIMP_UNIT_INCH;
+ else
+ return FALSE;
+ ptr += 2;
+ break;
+
+ case '%':
+ unit = GIMP_UNIT_PERCENT;
+ ptr += 1;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ while (g_ascii_isspace (*ptr))
+ ptr++;
+
+ if (*ptr)
+ return FALSE;
+
+ switch (unit)
+ {
+ case GIMP_UNIT_PERCENT:
+ *length = len * reference / 100.0;
+ break;
+
+ case GIMP_UNIT_PIXEL:
+ *length = len;
+ break;
+
+ default:
+ *length = len * resolution / gimp_unit_get_factor (unit);
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+parse_svg_viewbox (const gchar *value,
+ gdouble *width,
+ gdouble *height,
+ GimpMatrix3 *matrix)
+{
+ gdouble x, y, w, h;
+ gchar *tok;
+ gchar *str = g_strdup (value);
+ gboolean success = FALSE;
+
+ x = y = w = h = 0;
+
+ tok = strtok (str, ", \t");
+ if (tok)
+ {
+ x = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok)
+ {
+ y = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok != NULL)
+ {
+ w = g_ascii_strtod (tok, NULL);
+ tok = strtok (NULL, ", \t");
+ if (tok)
+ {
+ h = g_ascii_strtod (tok, NULL);
+ success = TRUE;
+ }
+ }
+ }
+ }
+
+ g_free (str);
+
+ if (success)
+ {
+ gimp_matrix3_identity (matrix);
+ gimp_matrix3_translate (matrix, -x, -y);
+
+ if (w > 0.0 && h > 0.0)
+ {
+ gimp_matrix3_scale (matrix, *width / w, *height / h);
+ }
+ else /* disable rendering of the element */
+ {
+#ifdef DEBUG_VECTORS_IMPORT
+ g_printerr ("empty viewBox");
+#endif
+ *width = *height = 0.0;
+ }
+ }
+ else
+ {
+ g_printerr ("SVG import: cannot parse viewBox attribute\n");
+ }
+
+ return success;
+}
+
+static gboolean
+parse_svg_transform (const gchar *value,
+ GimpMatrix3 *matrix)
+{
+ gint i;
+
+ gimp_matrix3_identity (matrix);
+
+ for (i = 0; value[i]; i++)
+ {
+ GimpMatrix3 trafo;
+ gchar keyword[32];
+ gdouble args[6];
+ gint n_args;
+ gint key_len;
+
+ gimp_matrix3_identity (&trafo);
+
+ /* skip initial whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ /* parse keyword */
+ for (key_len = 0; key_len < sizeof (keyword); key_len++)
+ {
+ gchar c = value[i];
+
+ if (g_ascii_isalpha (c) || c == '-')
+ keyword[key_len] = value[i++];
+ else
+ break;
+ }
+
+ if (key_len >= sizeof (keyword))
+ return FALSE;
+
+ keyword[key_len] = '\0';
+
+ /* skip whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ if (value[i] != '(')
+ return FALSE;
+ i++;
+
+ for (n_args = 0; ; n_args++)
+ {
+ gchar c;
+ gchar *end_ptr;
+
+ /* skip whitespace */
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ c = value[i];
+ if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.')
+ {
+ if (n_args == G_N_ELEMENTS (args))
+ return FALSE; /* too many args */
+
+ args[n_args] = g_ascii_strtod (value + i, &end_ptr);
+ i = end_ptr - value;
+
+ while (g_ascii_isspace (value[i]))
+ i++;
+
+ /* skip optional comma */
+ if (value[i] == ',')
+ i++;
+ }
+ else if (c == ')')
+ break;
+ else
+ return FALSE;
+ }
+
+ /* OK, have parsed keyword and args, now calculate the transform matrix */
+
+ if (strcmp (keyword, "matrix") == 0)
+ {
+ if (n_args != 6)
+ return FALSE;
+
+ gimp_matrix3_affine (&trafo,
+ args[0], args[1],
+ args[2], args[3],
+ args[4], args[5]);
+ }
+ else if (strcmp (keyword, "translate") == 0)
+ {
+ if (n_args == 1)
+ args[1] = 0.0;
+ else if (n_args != 2)
+ return FALSE;
+
+ gimp_matrix3_translate (&trafo, args[0], args[1]);
+ }
+ else if (strcmp (keyword, "scale") == 0)
+ {
+ if (n_args == 1)
+ args[1] = args[0];
+ else if (n_args != 2)
+ return FALSE;
+
+ gimp_matrix3_scale (&trafo, args[0], args[1]);
+ }
+ else if (strcmp (keyword, "rotate") == 0)
+ {
+ if (n_args == 1)
+ {
+ gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0]));
+ }
+ else if (n_args == 3)
+ {
+ gimp_matrix3_translate (&trafo, -args[1], -args[2]);
+ gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0]));
+ gimp_matrix3_translate (&trafo, args[1], args[2]);
+ }
+ else
+ return FALSE;
+ }
+ else if (strcmp (keyword, "skewX") == 0)
+ {
+ if (n_args != 1)
+ return FALSE;
+
+ gimp_matrix3_xshear (&trafo, tan (gimp_deg_to_rad (args[0])));
+ }
+ else if (strcmp (keyword, "skewY") == 0)
+ {
+ if (n_args != 1)
+ return FALSE;
+
+ gimp_matrix3_yshear (&trafo, tan (gimp_deg_to_rad (args[0])));
+ }
+ else
+ {
+ return FALSE; /* unknown keyword */
+ }
+
+ gimp_matrix3_invert (&trafo);
+ gimp_matrix3_mult (&trafo, matrix);
+ }
+
+ gimp_matrix3_invert (matrix);
+
+ return TRUE;
+}
+
+
+/**********************************************************/
+/* Below is the code that parses the actual path data. */
+/* */
+/* This code is taken from librsvg and was originally */
+/* written by Raph Levien <raph@artofcode.com> for Gill. */
+/**********************************************************/
+
+typedef struct
+{
+ GList *strokes;
+ GimpStroke *stroke;
+ gdouble cpx, cpy; /* current point */
+ gdouble rpx, rpy; /* reflection point (for 's' and 't' commands) */
+ gchar cmd; /* current command (lowercase) */
+ gint param; /* number of parameters */
+ gboolean rel; /* true if relative coords */
+ gdouble params[7]; /* parameters that have been parsed */
+} ParsePathContext;
+
+
+static void parse_path_default_xy (ParsePathContext *ctx,
+ gint n_params);
+static void parse_path_do_cmd (ParsePathContext *ctx,
+ gboolean final);
+
+
+static GList *
+parse_path_data (const gchar *data)
+{
+ ParsePathContext ctx;
+
+ gboolean in_num = FALSE;
+ gboolean in_frac = FALSE;
+ gboolean in_exp = FALSE;
+ gboolean exp_wait_sign = FALSE;
+ gdouble val = 0.0;
+ gchar c = 0;
+ gint sign = 0;
+ gint exp = 0;
+ gint exp_sign = 0;
+ gdouble frac = 0.0;
+ gint i;
+
+ memset (&ctx, 0, sizeof (ParsePathContext));
+
+ for (i = 0; ; i++)
+ {
+ c = data[i];
+
+ if (c >= '0' && c <= '9')
+ {
+ /* digit */
+ if (in_num)
+ {
+ if (in_exp)
+ {
+ exp = (exp * 10) + c - '0';
+ exp_wait_sign = FALSE;
+ }
+ else if (in_frac)
+ val += (frac *= 0.1) * (c - '0');
+ else
+ val = (val * 10) + c - '0';
+ }
+ else
+ {
+ in_num = TRUE;
+ in_frac = FALSE;
+ in_exp = FALSE;
+ exp = 0;
+ exp_sign = 1;
+ exp_wait_sign = FALSE;
+ val = c - '0';
+ sign = 1;
+ }
+ }
+ else if (c == '.')
+ {
+ if (! in_num)
+ {
+ in_num = TRUE;
+ val = 0;
+ }
+
+ in_frac = TRUE;
+ frac = 1;
+ }
+ else if ((c == 'E' || c == 'e') && in_num)
+ {
+ in_exp = TRUE;
+ exp_wait_sign = TRUE;
+ exp = 0;
+ exp_sign = 1;
+ }
+ else if ((c == '+' || c == '-') && in_exp)
+ {
+ exp_sign = c == '+' ? 1 : -1;
+ }
+ else if (in_num)
+ {
+ /* end of number */
+
+ val *= sign * pow (10, exp_sign * exp);
+
+ if (ctx.rel)
+ {
+ /* Handle relative coordinates. This switch statement attempts
+ to determine _what_ the coords are relative to. This is
+ underspecified in the 12 Apr working draft. */
+ switch (ctx.cmd)
+ {
+ case 'l':
+ case 'm':
+ case 'c':
+ case 's':
+ case 'q':
+ case 't':
+ /* rule: even-numbered params are x-relative, odd-numbered
+ are y-relative */
+ if ((ctx.param & 1) == 0)
+ val += ctx.cpx;
+ else if ((ctx.param & 1) == 1)
+ val += ctx.cpy;
+ break;
+
+ case 'a':
+ /* rule: sixth and seventh are x and y, rest are not
+ relative */
+ if (ctx.param == 5)
+ val += ctx.cpx;
+ else if (ctx.param == 6)
+ val += ctx.cpy;
+ break;
+
+ case 'h':
+ /* rule: x-relative */
+ val += ctx.cpx;
+ break;
+
+ case 'v':
+ /* rule: y-relative */
+ val += ctx.cpy;
+ break;
+ }
+ }
+
+ ctx.params[ctx.param++] = val;
+ parse_path_do_cmd (&ctx, FALSE);
+ in_num = FALSE;
+ }
+
+ if (c == '\0')
+ {
+ break;
+ }
+ else if ((c == '+' || c == '-') && ! exp_wait_sign)
+ {
+ sign = c == '+' ? 1 : -1;
+ val = 0;
+ in_num = TRUE;
+ in_frac = FALSE;
+ in_exp = FALSE;
+ exp = 0;
+ exp_sign = 1;
+ exp_wait_sign = FALSE;
+ }
+ else if (c == 'z' || c == 'Z')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ if (ctx.stroke)
+ gimp_stroke_close (ctx.stroke);
+ }
+ else if (c >= 'A' && c <= 'Z' && c != 'E')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ ctx.cmd = c + 'a' - 'A';
+ ctx.rel = FALSE;
+ }
+ else if (c >= 'a' && c <= 'z' && c != 'e')
+ {
+ if (ctx.param)
+ parse_path_do_cmd (&ctx, TRUE);
+
+ ctx.cmd = c;
+ ctx.rel = TRUE;
+ }
+ /* else c _should_ be whitespace or , */
+ }
+
+ return g_list_reverse (ctx.strokes);
+}
+
+/* supply defaults for missing parameters, assuming relative coordinates
+ are to be interpreted as x,y */
+static void
+parse_path_default_xy (ParsePathContext *ctx,
+ gint n_params)
+{
+ gint i;
+
+ if (ctx->rel)
+ {
+ for (i = ctx->param; i < n_params; i++)
+ {
+ if (i > 2)
+ ctx->params[i] = ctx->params[i - 2];
+ else if (i == 1)
+ ctx->params[i] = ctx->cpy;
+ else if (i == 0)
+ /* we shouldn't get here (ctx->param > 0 as precondition) */
+ ctx->params[i] = ctx->cpx;
+ }
+ }
+ else
+ {
+ for (i = ctx->param; i < n_params; i++)
+ ctx->params[i] = 0.0;
+ }
+}
+
+static void
+parse_path_do_cmd (ParsePathContext *ctx,
+ gboolean final)
+{
+ GimpCoords coords = COORDS_INIT;
+
+ switch (ctx->cmd)
+ {
+ case 'm':
+ /* moveto */
+ if (ctx->param == 2 || final)
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ ctx->stroke = gimp_bezier_stroke_new_moveto (&coords);
+ ctx->strokes = g_list_prepend (ctx->strokes, ctx->stroke);
+
+ ctx->param = 0;
+
+ /* If a moveto is followed by multiple pairs of coordinates,
+ * the subsequent pairs are treated as implicit lineto commands.
+ */
+ ctx->cmd = 'l';
+ }
+ break;
+
+ case 'l':
+ /* lineto */
+ if (ctx->param == 2 || final)
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'c':
+ /* curveto */
+ if (ctx->param == 6 || final)
+ {
+ GimpCoords ctrl1 = COORDS_INIT;
+ GimpCoords ctrl2 = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 6);
+
+ ctrl1.x = ctx->params[0];
+ ctrl1.y = ctx->params[1];
+ ctrl2.x = ctx->rpx = ctx->params[2];
+ ctrl2.y = ctx->rpy = ctx->params[3];
+ coords.x = ctx->cpx = ctx->params[4];
+ coords.y = ctx->cpy = ctx->params[5];
+
+ gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 's':
+ /* smooth curveto */
+ if (ctx->param == 4 || final)
+ {
+ GimpCoords ctrl1 = COORDS_INIT;
+ GimpCoords ctrl2 = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl1.x = 2 * ctx->cpx - ctx->rpx;
+ ctrl1.y = 2 * ctx->cpy - ctx->rpy;
+ ctrl2.x = ctx->rpx = ctx->params[0];
+ ctrl2.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'h':
+ /* horizontal lineto */
+ if (ctx->param == 1)
+ {
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy;
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'v':
+ /* vertical lineto */
+ if (ctx->param == 1)
+ {
+ coords.x = ctx->cpx;
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[0];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'q':
+ /* quadratic bezier curveto */
+ if (ctx->param == 4 || final)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl.x = ctx->rpx = ctx->params[0];
+ ctrl.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 't':
+ /* truetype quadratic bezier curveto */
+ if (ctx->param == 2 || final)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 2);
+
+ ctrl.x = ctx->rpx = 2 * ctx->cpx - ctx->rpx;
+ ctrl.y = ctx->rpy = 2 * ctx->cpy - ctx->rpy;
+ coords.x = ctx->cpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->params[1];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+
+ ctx->param = 0;
+ }
+ else if (final)
+ {
+ if (ctx->param > 2)
+ {
+ GimpCoords ctrl = COORDS_INIT;
+
+ parse_path_default_xy (ctx, 4);
+
+ ctrl.x = ctx->rpx = ctx->params[0];
+ ctrl.y = ctx->rpy = ctx->params[1];
+ coords.x = ctx->cpx = ctx->params[2];
+ coords.y = ctx->cpy = ctx->params[3];
+
+ gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords);
+ }
+ else
+ {
+ parse_path_default_xy (ctx, 2);
+
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[0];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[1];
+
+ gimp_bezier_stroke_lineto (ctx->stroke, &coords);
+ }
+
+ ctx->param = 0;
+ }
+ break;
+
+ case 'a':
+ if (ctx->param == 7 || final)
+ {
+ coords.x = ctx->cpx = ctx->rpx = ctx->params[5];
+ coords.y = ctx->cpy = ctx->rpy = ctx->params[6];
+
+ gimp_bezier_stroke_arcto (ctx->stroke,
+ ctx->params[0], ctx->params[1],
+ gimp_deg_to_rad (ctx->params[2]),
+ ctx->params[3], ctx->params[4],
+ &coords);
+ ctx->param = 0;
+ }
+ break;
+
+ default:
+ ctx->param = 0;
+ break;
+ }
+}
diff --git a/app/vectors/gimpvectors-import.h b/app/vectors/gimpvectors-import.h
new file mode 100644
index 0000000..1d7f8a6
--- /dev/null
+++ b/app/vectors/gimpvectors-import.h
@@ -0,0 +1,44 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpVectors Import
+ * Copyright (C) 2003 Sven Neumann <sven@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_IMPORT_H__
+#define __GIMP_VECTORS_IMPORT_H__
+
+
+gboolean gimp_vectors_import_file (GimpImage *image,
+ GFile *file,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error);
+gboolean gimp_vectors_import_buffer (GimpImage *image,
+ const gchar *buffer,
+ gsize len,
+ gboolean merge,
+ gboolean scale,
+ GimpVectors *parent,
+ gint position,
+ GList **ret_vectors,
+ GError **error);
+
+
+#endif /* __GIMP_VECTORS_IMPORT_H__ */
diff --git a/app/vectors/gimpvectors-preview.c b/app/vectors/gimpvectors-preview.c
new file mode 100644
index 0000000..988148a
--- /dev/null
+++ b/app/vectors/gimpvectors-preview.c
@@ -0,0 +1,93 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core/gimpimage.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-preview.h"
+
+
+/* public functions */
+
+GimpTempBuf *
+gimp_vectors_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height)
+{
+ GimpVectors *vectors;
+ GimpItem *item;
+ GimpStroke *cur_stroke;
+ gdouble xscale, yscale;
+ guchar *data;
+ GimpTempBuf *temp_buf;
+
+ vectors = GIMP_VECTORS (viewable);
+ item = GIMP_ITEM (viewable);
+
+ xscale = ((gdouble) width) / gimp_image_get_width (gimp_item_get_image (item));
+ yscale = ((gdouble) height) / gimp_image_get_height (gimp_item_get_image (item));
+
+ temp_buf = gimp_temp_buf_new (width, height, babl_format ("Y' u8"));
+ data = gimp_temp_buf_get_data (temp_buf);
+ memset (data, 255, width * height);
+
+ for (cur_stroke = gimp_vectors_stroke_get_next (vectors, NULL);
+ cur_stroke;
+ cur_stroke = gimp_vectors_stroke_get_next (vectors, cur_stroke))
+ {
+ GArray *coords;
+ gboolean closed;
+ gint i;
+
+ coords = gimp_stroke_interpolate (cur_stroke, 0.5, &closed);
+
+ if (coords)
+ {
+ for (i = 0; i < coords->len; i++)
+ {
+ GimpCoords point;
+ gint x, y;
+
+ point = g_array_index (coords, GimpCoords, i);
+
+ x = ROUND (point.x * xscale);
+ y = ROUND (point.y * yscale);
+
+ if (x >= 0 && y >= 0 && x < width && y < height)
+ data[y * width + x] = 0;
+ }
+
+ g_array_free (coords, TRUE);
+ }
+ }
+
+ return temp_buf;
+}
diff --git a/app/vectors/gimpvectors-preview.h b/app/vectors/gimpvectors-preview.h
new file mode 100644
index 0000000..f934d8e
--- /dev/null
+++ b/app/vectors/gimpvectors-preview.h
@@ -0,0 +1,32 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_PREVIEW_H__
+#define __GIMP_VECTORS_PREVIEW_H__
+
+
+/*
+ * virtual function of GimpVectors -- don't call directly
+ */
+
+GimpTempBuf * gimp_vectors_get_new_preview (GimpViewable *viewable,
+ GimpContext *context,
+ gint width,
+ gint height);
+
+
+#endif /* __GIMP_VECTORS_PREVIEW_H__ */
diff --git a/app/vectors/gimpvectors-warp.c b/app/vectors/gimpvectors-warp.c
new file mode 100644
index 0000000..aeb8dec
--- /dev/null
+++ b/app/vectors/gimpvectors-warp.c
@@ -0,0 +1,210 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors-warp.c
+ * Copyright (C) 2005 Bill Skaggs <weskaggs@primate.ucdavis.edu>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "libgimpmath/gimpmath.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpcoords.h"
+
+#include "gimpanchor.h"
+#include "gimpstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-warp.h"
+
+
+#define EPSILON 0.2
+#define DX 2.0
+
+
+static void gimp_stroke_warp_point (GimpStroke *stroke,
+ gdouble x,
+ gdouble y,
+ GimpCoords *point_warped,
+ gdouble y_offset,
+ gdouble x_len);
+
+static void gimp_vectors_warp_stroke (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble y_offset);
+
+
+void
+gimp_vectors_warp_point (GimpVectors *vectors,
+ GimpCoords *point,
+ GimpCoords *point_warped,
+ gdouble y_offset)
+{
+ gdouble x = point->x;
+ gdouble y = point->y;
+ gdouble len;
+ GList *list;
+ GimpStroke *stroke;
+
+ for (list = vectors->strokes->head;
+ list;
+ list = g_list_next (list))
+ {
+ stroke = list->data;
+
+ len = gimp_vectors_stroke_get_length (vectors, stroke);
+
+ if (x < len || ! list->next)
+ break;
+
+ x -= len;
+ }
+
+ if (! list)
+ {
+ point_warped->x = 0;
+ point_warped->y = 0;
+ return;
+ }
+
+ gimp_stroke_warp_point (stroke, x, y, point_warped, y_offset, len);
+}
+
+static void
+gimp_stroke_warp_point (GimpStroke *stroke,
+ gdouble x,
+ gdouble y,
+ GimpCoords *point_warped,
+ gdouble y_offset,
+ gdouble x_len)
+{
+ GimpCoords point_zero = { 0, };
+ GimpCoords point_minus = { 0, };
+ GimpCoords point_plus = { 0, };
+ gdouble slope;
+ gdouble dx, dy, nx, ny, len;
+
+ if (x + DX >= x_len)
+ {
+ gdouble tx, ty;
+
+ if (! gimp_stroke_get_point_at_dist (stroke, x_len, EPSILON,
+ &point_zero, &slope))
+ {
+ point_warped->x = 0;
+ point_warped->y = 0;
+ return;
+ }
+
+ point_warped->x = point_zero.x;
+ point_warped->y = point_zero.y;
+
+ if (! gimp_stroke_get_point_at_dist (stroke, x_len - DX, EPSILON,
+ &point_minus, &slope))
+ return;
+
+ dx = point_zero.x - point_minus.x;
+ dy = point_zero.y - point_minus.y;
+
+ len = hypot (dx, dy);
+
+ if (len < 0.01)
+ return;
+
+ tx = dx / len;
+ ty = dy / len;
+
+ nx = - dy / len;
+ ny = dx / len;
+
+ point_warped->x += tx * (x - x_len) + nx * (y - y_offset);
+ point_warped->y += ty * (x - x_len) + ny * (y - y_offset);
+
+ return;
+ }
+
+ if (! gimp_stroke_get_point_at_dist (stroke, x, EPSILON,
+ &point_zero, &slope))
+ {
+ point_warped->x = 0;
+ point_warped->y = 0;
+ return;
+ }
+
+ point_warped->x = point_zero.x;
+ point_warped->y = point_zero.y;
+
+ if (! gimp_stroke_get_point_at_dist (stroke, x - DX, EPSILON,
+ &point_minus, &slope))
+ return;
+
+ if (! gimp_stroke_get_point_at_dist (stroke, x + DX, EPSILON,
+ &point_plus, &slope))
+ return;
+
+ dx = point_plus.x - point_minus.x;
+ dy = point_plus.y - point_minus.y;
+
+ len = hypot (dx, dy);
+
+ if (len < 0.01)
+ return;
+
+ nx = - dy / len;
+ ny = dx / len;
+
+ point_warped->x = point_zero.x + nx * (y - y_offset);
+ point_warped->y = point_zero.y + ny * (y - y_offset);
+}
+
+static void
+gimp_vectors_warp_stroke (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble y_offset)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ gimp_vectors_warp_point (vectors,
+ &anchor->position, &anchor->position,
+ y_offset);
+ }
+}
+
+void
+gimp_vectors_warp_vectors (GimpVectors *vectors,
+ GimpVectors *vectors_in,
+ gdouble y_offset)
+{
+ GList *list;
+
+ for (list = vectors_in->strokes->head;
+ list;
+ list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_vectors_warp_stroke (vectors, stroke, y_offset);
+ }
+}
diff --git a/app/vectors/gimpvectors-warp.h b/app/vectors/gimpvectors-warp.h
new file mode 100644
index 0000000..12b8993
--- /dev/null
+++ b/app/vectors/gimpvectors-warp.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors-warp.h
+ * Copyright (C) 2005 Bill Skaggs <weskaggs@primate.ucdavis.edu>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_WARP_H__
+#define __GIMP_VECTORS_WARP_H__
+
+
+void gimp_vectors_warp_point (GimpVectors *vectors,
+ GimpCoords *point,
+ GimpCoords *point_warped,
+ gdouble y_offset);
+
+void gimp_vectors_warp_vectors (GimpVectors *vectors,
+ GimpVectors *vectors_in,
+ gdouble yoffset);
+
+
+#endif /* __GIMP_VECTORS_WARP_H__ */
+
diff --git a/app/vectors/gimpvectors.c b/app/vectors/gimpvectors.c
new file mode 100644
index 0000000..1a65eac
--- /dev/null
+++ b/app/vectors/gimpvectors.c
@@ -0,0 +1,1255 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "core/gimp.h"
+#include "core/gimp-transform-utils.h"
+#include "core/gimpbezierdesc.h"
+#include "core/gimpchannel-select.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpcontext.h"
+#include "core/gimpdrawable-fill.h"
+#include "core/gimpdrawable-stroke.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-undo-push.h"
+#include "core/gimpmarshal.h"
+#include "core/gimppaintinfo.h"
+#include "core/gimpstrokeoptions.h"
+
+#include "paint/gimppaintcore-stroke.h"
+#include "paint/gimppaintoptions.h"
+
+#include "gimpanchor.h"
+#include "gimpstroke.h"
+#include "gimpvectors.h"
+#include "gimpvectors-preview.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ FREEZE,
+ THAW,
+ LAST_SIGNAL
+};
+
+
+static void gimp_vectors_finalize (GObject *object);
+
+static gint64 gimp_vectors_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_vectors_is_attached (GimpItem *item);
+static GimpItemTree * gimp_vectors_get_tree (GimpItem *item);
+static gboolean gimp_vectors_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height);
+static GimpItem * gimp_vectors_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_vectors_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type);
+static void gimp_vectors_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo);
+static void gimp_vectors_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_vectors_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_vectors_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_vectors_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_vectors_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interp_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+static GimpTransformResize
+ gimp_vectors_get_clip (GimpItem *item,
+ GimpTransformResize clip_result);
+static gboolean gimp_vectors_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static gboolean gimp_vectors_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error);
+static void gimp_vectors_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y);
+
+static void gimp_vectors_real_freeze (GimpVectors *vectors);
+static void gimp_vectors_real_thaw (GimpVectors *vectors);
+static void gimp_vectors_real_stroke_add (GimpVectors *vectors,
+ GimpStroke *stroke);
+static void gimp_vectors_real_stroke_remove (GimpVectors *vectors,
+ GimpStroke *stroke);
+static GimpStroke * gimp_vectors_real_stroke_get (GimpVectors *vectors,
+ const GimpCoords *coord);
+static GimpStroke *gimp_vectors_real_stroke_get_next(GimpVectors *vectors,
+ GimpStroke *prev);
+static gdouble gimp_vectors_real_stroke_get_length (GimpVectors *vectors,
+ GimpStroke *prev);
+static GimpAnchor * gimp_vectors_real_anchor_get (GimpVectors *vectors,
+ const GimpCoords *coord,
+ GimpStroke **ret_stroke);
+static void gimp_vectors_real_anchor_delete (GimpVectors *vectors,
+ GimpAnchor *anchor);
+static gdouble gimp_vectors_real_get_length (GimpVectors *vectors,
+ const GimpAnchor *start);
+static gdouble gimp_vectors_real_get_distance (GimpVectors *vectors,
+ const GimpCoords *coord);
+static gint gimp_vectors_real_interpolate (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble precision,
+ gint max_points,
+ GimpCoords *ret_coords);
+
+static GimpBezierDesc * gimp_vectors_make_bezier (GimpVectors *vectors);
+static GimpBezierDesc * gimp_vectors_real_make_bezier (GimpVectors *vectors);
+
+
+G_DEFINE_TYPE (GimpVectors, gimp_vectors, GIMP_TYPE_ITEM)
+
+#define parent_class gimp_vectors_parent_class
+
+static guint gimp_vectors_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_vectors_class_init (GimpVectorsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+
+ gimp_vectors_signals[FREEZE] =
+ g_signal_new ("freeze",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpVectorsClass, freeze),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_vectors_signals[THAW] =
+ g_signal_new ("thaw",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpVectorsClass, thaw),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->finalize = gimp_vectors_finalize;
+
+ gimp_object_class->get_memsize = gimp_vectors_get_memsize;
+
+ viewable_class->get_new_preview = gimp_vectors_get_new_preview;
+ viewable_class->default_icon_name = "gimp-path";
+
+ item_class->is_attached = gimp_vectors_is_attached;
+ item_class->get_tree = gimp_vectors_get_tree;
+ item_class->bounds = gimp_vectors_bounds;
+ item_class->duplicate = gimp_vectors_duplicate;
+ item_class->convert = gimp_vectors_convert;
+ item_class->translate = gimp_vectors_translate;
+ item_class->scale = gimp_vectors_scale;
+ item_class->resize = gimp_vectors_resize;
+ item_class->flip = gimp_vectors_flip;
+ item_class->rotate = gimp_vectors_rotate;
+ item_class->transform = gimp_vectors_transform;
+ item_class->get_clip = gimp_vectors_get_clip;
+ item_class->fill = gimp_vectors_fill;
+ item_class->stroke = gimp_vectors_stroke;
+ item_class->to_selection = gimp_vectors_to_selection;
+ item_class->default_name = _("Path");
+ item_class->rename_desc = C_("undo-type", "Rename Path");
+ item_class->translate_desc = C_("undo-type", "Move Path");
+ item_class->scale_desc = C_("undo-type", "Scale Path");
+ item_class->resize_desc = C_("undo-type", "Resize Path");
+ item_class->flip_desc = C_("undo-type", "Flip Path");
+ item_class->rotate_desc = C_("undo-type", "Rotate Path");
+ item_class->transform_desc = C_("undo-type", "Transform Path");
+ item_class->fill_desc = C_("undo-type", "Fill Path");
+ item_class->stroke_desc = C_("undo-type", "Stroke Path");
+ item_class->to_selection_desc = C_("undo-type", "Path to Selection");
+ item_class->reorder_desc = C_("undo-type", "Reorder Path");
+ item_class->raise_desc = C_("undo-type", "Raise Path");
+ item_class->raise_to_top_desc = C_("undo-type", "Raise Path to Top");
+ item_class->lower_desc = C_("undo-type", "Lower Path");
+ item_class->lower_to_bottom_desc = C_("undo-type", "Lower Path to Bottom");
+ item_class->raise_failed = _("Path cannot be raised higher.");
+ item_class->lower_failed = _("Path cannot be lowered more.");
+
+ klass->freeze = gimp_vectors_real_freeze;
+ klass->thaw = gimp_vectors_real_thaw;
+
+ klass->stroke_add = gimp_vectors_real_stroke_add;
+ klass->stroke_remove = gimp_vectors_real_stroke_remove;
+ klass->stroke_get = gimp_vectors_real_stroke_get;
+ klass->stroke_get_next = gimp_vectors_real_stroke_get_next;
+ klass->stroke_get_length = gimp_vectors_real_stroke_get_length;
+
+ klass->anchor_get = gimp_vectors_real_anchor_get;
+ klass->anchor_delete = gimp_vectors_real_anchor_delete;
+
+ klass->get_length = gimp_vectors_real_get_length;
+ klass->get_distance = gimp_vectors_real_get_distance;
+ klass->interpolate = gimp_vectors_real_interpolate;
+
+ klass->make_bezier = gimp_vectors_real_make_bezier;
+}
+
+static void
+gimp_vectors_init (GimpVectors *vectors)
+{
+ gimp_item_set_visible (GIMP_ITEM (vectors), FALSE, FALSE);
+
+ vectors->strokes = g_queue_new ();
+ vectors->stroke_to_list = g_hash_table_new (g_direct_hash, g_direct_equal);
+ vectors->last_stroke_ID = 0;
+ vectors->freeze_count = 0;
+ vectors->precision = 0.2;
+
+ vectors->bezier_desc = NULL;
+ vectors->bounds_valid = FALSE;
+}
+
+static void
+gimp_vectors_finalize (GObject *object)
+{
+ GimpVectors *vectors = GIMP_VECTORS (object);
+
+ if (vectors->bezier_desc)
+ {
+ gimp_bezier_desc_free (vectors->bezier_desc);
+ vectors->bezier_desc = NULL;
+ }
+
+ if (vectors->strokes)
+ {
+ g_queue_free_full (vectors->strokes, (GDestroyNotify) g_object_unref);
+ vectors->strokes = NULL;
+ }
+
+ if (vectors->stroke_to_list)
+ {
+ g_hash_table_destroy (vectors->stroke_to_list);
+ vectors->stroke_to_list = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_vectors_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpVectors *vectors;
+ GList *list;
+ gint64 memsize = 0;
+
+ vectors = GIMP_VECTORS (object);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ memsize += (gimp_object_get_memsize (GIMP_OBJECT (list->data), gui_size) +
+ sizeof (GList));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_vectors_is_attached (GimpItem *item)
+{
+ GimpImage *image = gimp_item_get_image (item);
+
+ return (GIMP_IS_IMAGE (image) &&
+ gimp_container_have (gimp_image_get_vectors (image),
+ GIMP_OBJECT (item)));
+}
+
+static GimpItemTree *
+gimp_vectors_get_tree (GimpItem *item)
+{
+ if (gimp_item_is_attached (item))
+ {
+ GimpImage *image = gimp_item_get_image (item);
+
+ return gimp_image_get_vectors_tree (image);
+ }
+
+ return NULL;
+}
+
+static gboolean
+gimp_vectors_bounds (GimpItem *item,
+ gdouble *x,
+ gdouble *y,
+ gdouble *width,
+ gdouble *height)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+
+ if (! vectors->bounds_valid)
+ {
+ GimpStroke *stroke;
+
+ vectors->bounds_empty = TRUE;
+ vectors->bounds_x1 = vectors->bounds_x2 = 0.0;
+ vectors->bounds_y1 = vectors->bounds_y2 = 0.0;
+
+ for (stroke = gimp_vectors_stroke_get_next (vectors, NULL);
+ stroke;
+ stroke = gimp_vectors_stroke_get_next (vectors, stroke))
+ {
+ GArray *stroke_coords;
+ gboolean closed;
+
+ stroke_coords = gimp_stroke_interpolate (stroke, 1.0, &closed);
+
+ if (stroke_coords)
+ {
+ GimpCoords point;
+ gint i;
+
+ if (vectors->bounds_empty && stroke_coords->len > 0)
+ {
+ point = g_array_index (stroke_coords, GimpCoords, 0);
+
+ vectors->bounds_x1 = vectors->bounds_x2 = point.x;
+ vectors->bounds_y1 = vectors->bounds_y2 = point.y;
+
+ vectors->bounds_empty = FALSE;
+ }
+
+ for (i = 0; i < stroke_coords->len; i++)
+ {
+ point = g_array_index (stroke_coords, GimpCoords, i);
+
+ vectors->bounds_x1 = MIN (vectors->bounds_x1, point.x);
+ vectors->bounds_y1 = MIN (vectors->bounds_y1, point.y);
+ vectors->bounds_x2 = MAX (vectors->bounds_x2, point.x);
+ vectors->bounds_y2 = MAX (vectors->bounds_y2, point.y);
+ }
+
+ g_array_free (stroke_coords, TRUE);
+ }
+ }
+
+ vectors->bounds_valid = TRUE;
+ }
+
+ *x = vectors->bounds_x1;
+ *y = vectors->bounds_y1;
+ *width = vectors->bounds_x2 - vectors->bounds_x1;
+ *height = vectors->bounds_y2 - vectors->bounds_y1;
+
+ return ! vectors->bounds_empty;
+}
+
+static GimpItem *
+gimp_vectors_duplicate (GimpItem *item,
+ GType new_type)
+{
+ GimpItem *new_item;
+
+ g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_VECTORS), NULL);
+
+ new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
+
+ if (GIMP_IS_VECTORS (new_item))
+ {
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GimpVectors *new_vectors = GIMP_VECTORS (new_item);
+
+ gimp_vectors_copy_strokes (vectors, new_vectors);
+ }
+
+ return new_item;
+}
+
+static void
+gimp_vectors_convert (GimpItem *item,
+ GimpImage *dest_image,
+ GType old_type)
+{
+ gimp_item_set_size (item,
+ gimp_image_get_width (dest_image),
+ gimp_image_get_height (dest_image));
+
+ GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type);
+}
+
+static void
+gimp_vectors_translate (GimpItem *item,
+ gdouble offset_x,
+ gdouble offset_y,
+ gboolean push_undo)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GList *list;
+
+ gimp_vectors_freeze (vectors);
+
+ if (push_undo)
+ gimp_image_undo_push_vectors_mod (gimp_item_get_image (item),
+ _("Move Path"),
+ vectors);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_translate (stroke, offset_x, offset_y);
+ }
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GimpImage *image = gimp_item_get_image (item);
+ GList *list;
+
+ gimp_vectors_freeze (vectors);
+
+ if (gimp_item_is_attached (item))
+ gimp_image_undo_push_vectors_mod (image, NULL, vectors);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_scale (stroke,
+ (gdouble) new_width / (gdouble) gimp_item_get_width (item),
+ (gdouble) new_height / (gdouble) gimp_item_get_height (item));
+ gimp_stroke_translate (stroke, new_offset_x, new_offset_y);
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->scale (item,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ 0, 0,
+ interpolation_type, progress);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GimpImage *image = gimp_item_get_image (item);
+ GList *list;
+
+ gimp_vectors_freeze (vectors);
+
+ if (gimp_item_is_attached (item))
+ gimp_image_undo_push_vectors_mod (image, NULL, vectors);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_translate (stroke, offset_x, offset_y);
+ }
+
+ GIMP_ITEM_CLASS (parent_class)->resize (item, context, fill_type,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image),
+ 0, 0);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GList *list;
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_flip (&matrix, flip_type, axis);
+
+ gimp_vectors_freeze (vectors);
+
+ gimp_image_undo_push_vectors_mod (gimp_item_get_image (item),
+ _("Flip Path"),
+ vectors);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+ }
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GList *list;
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_rotate (&matrix, rotate_type, center_x, center_y);
+
+ gimp_vectors_freeze (vectors);
+
+ gimp_image_undo_push_vectors_mod (gimp_item_get_image (item),
+ _("Rotate Path"),
+ vectors);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+ }
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GimpMatrix3 local_matrix;
+ GQueue strokes;
+ GList *list;
+
+ gimp_vectors_freeze (vectors);
+
+ gimp_image_undo_push_vectors_mod (gimp_item_get_image (item),
+ _("Transform Path"),
+ vectors);
+
+ local_matrix = *matrix;
+
+ if (direction == GIMP_TRANSFORM_BACKWARD)
+ gimp_matrix3_invert (&local_matrix);
+
+ g_queue_init (&strokes);
+
+ while (! g_queue_is_empty (vectors->strokes))
+ {
+ GimpStroke *stroke = g_queue_peek_head (vectors->strokes);
+
+ g_object_ref (stroke);
+
+ gimp_vectors_stroke_remove (vectors, stroke);
+
+ gimp_stroke_transform (stroke, &local_matrix, &strokes);
+
+ g_object_unref (stroke);
+ }
+
+ vectors->last_stroke_ID = 0;
+
+ for (list = strokes.head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_vectors_stroke_add (vectors, stroke);
+
+ g_object_unref (stroke);
+ }
+
+ g_queue_clear (&strokes);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static GimpTransformResize
+gimp_vectors_get_clip (GimpItem *item,
+ GimpTransformResize clip_result)
+{
+ return GIMP_TRANSFORM_RESIZE_ADJUST;
+}
+
+static gboolean
+gimp_vectors_fill (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpFillOptions *fill_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+
+ if (g_queue_is_empty (vectors->strokes))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough points to fill"));
+ return FALSE;
+ }
+
+ return gimp_drawable_fill_vectors (drawable, fill_options,
+ vectors, push_undo, error);
+}
+
+static gboolean
+gimp_vectors_stroke (GimpItem *item,
+ GimpDrawable *drawable,
+ GimpStrokeOptions *stroke_options,
+ gboolean push_undo,
+ GimpProgress *progress,
+ GError **error)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ gboolean retval = FALSE;
+
+ if (g_queue_is_empty (vectors->strokes))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough points to stroke"));
+ return FALSE;
+ }
+
+ switch (gimp_stroke_options_get_method (stroke_options))
+ {
+ case GIMP_STROKE_LINE:
+ retval = gimp_drawable_stroke_vectors (drawable, stroke_options,
+ vectors, push_undo, error);
+ break;
+
+ case GIMP_STROKE_PAINT_METHOD:
+ {
+ GimpPaintInfo *paint_info;
+ GimpPaintCore *core;
+ GimpPaintOptions *paint_options;
+ gboolean emulate_dynamics;
+
+ paint_info = gimp_context_get_paint_info (GIMP_CONTEXT (stroke_options));
+
+ core = g_object_new (paint_info->paint_type, NULL);
+
+ paint_options = gimp_stroke_options_get_paint_options (stroke_options);
+ emulate_dynamics = gimp_stroke_options_get_emulate_dynamics (stroke_options);
+
+ retval = gimp_paint_core_stroke_vectors (core, drawable,
+ paint_options,
+ emulate_dynamics,
+ vectors, push_undo, error);
+
+ g_object_unref (core);
+ }
+ break;
+
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ return retval;
+}
+
+static void
+gimp_vectors_to_selection (GimpItem *item,
+ GimpChannelOps op,
+ gboolean antialias,
+ gboolean feather,
+ gdouble feather_radius_x,
+ gdouble feather_radius_y)
+{
+ GimpVectors *vectors = GIMP_VECTORS (item);
+ GimpImage *image = gimp_item_get_image (item);
+
+ gimp_channel_select_vectors (gimp_image_get_mask (image),
+ GIMP_ITEM_GET_CLASS (item)->to_selection_desc,
+ vectors,
+ op, antialias,
+ feather, feather_radius_x, feather_radius_x,
+ TRUE);
+}
+
+static void
+gimp_vectors_real_freeze (GimpVectors *vectors)
+{
+ /* release cached bezier representation */
+ if (vectors->bezier_desc)
+ {
+ gimp_bezier_desc_free (vectors->bezier_desc);
+ vectors->bezier_desc = NULL;
+ }
+
+ /* invalidate bounds */
+ vectors->bounds_valid = FALSE;
+}
+
+static void
+gimp_vectors_real_thaw (GimpVectors *vectors)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (vectors));
+}
+
+
+/* public functions */
+
+GimpVectors *
+gimp_vectors_new (GimpImage *image,
+ const gchar *name)
+{
+ GimpVectors *vectors;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+
+ vectors = GIMP_VECTORS (gimp_item_new (GIMP_TYPE_VECTORS,
+ image, name,
+ 0, 0,
+ gimp_image_get_width (image),
+ gimp_image_get_height (image)));
+
+ return vectors;
+}
+
+GimpVectors *
+gimp_vectors_get_parent (GimpVectors *vectors)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ return GIMP_VECTORS (gimp_viewable_get_parent (GIMP_VIEWABLE (vectors)));
+}
+
+void
+gimp_vectors_freeze (GimpVectors *vectors)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+
+ vectors->freeze_count++;
+
+ if (vectors->freeze_count == 1)
+ g_signal_emit (vectors, gimp_vectors_signals[FREEZE], 0);
+}
+
+void
+gimp_vectors_thaw (GimpVectors *vectors)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (vectors->freeze_count > 0);
+
+ vectors->freeze_count--;
+
+ if (vectors->freeze_count == 0)
+ g_signal_emit (vectors, gimp_vectors_signals[THAW], 0);
+}
+
+void
+gimp_vectors_copy_strokes (GimpVectors *src_vectors,
+ GimpVectors *dest_vectors)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (src_vectors));
+ g_return_if_fail (GIMP_IS_VECTORS (dest_vectors));
+
+ gimp_vectors_freeze (dest_vectors);
+
+ g_queue_free_full (dest_vectors->strokes, (GDestroyNotify) g_object_unref);
+ dest_vectors->strokes = g_queue_new ();
+ g_hash_table_remove_all (dest_vectors->stroke_to_list);
+
+ dest_vectors->last_stroke_ID = 0;
+
+ gimp_vectors_add_strokes (src_vectors, dest_vectors);
+
+ gimp_vectors_thaw (dest_vectors);
+}
+
+
+void
+gimp_vectors_add_strokes (GimpVectors *src_vectors,
+ GimpVectors *dest_vectors)
+{
+ GList *stroke;
+
+ g_return_if_fail (GIMP_IS_VECTORS (src_vectors));
+ g_return_if_fail (GIMP_IS_VECTORS (dest_vectors));
+
+ gimp_vectors_freeze (dest_vectors);
+
+ for (stroke = src_vectors->strokes->head;
+ stroke != NULL;
+ stroke = g_list_next (stroke))
+ {
+ GimpStroke *newstroke = gimp_stroke_duplicate (stroke->data);
+
+ g_queue_push_tail (dest_vectors->strokes, newstroke);
+
+ /* Also add to {stroke: GList node} map */
+ g_hash_table_insert (dest_vectors->stroke_to_list,
+ newstroke,
+ g_queue_peek_tail_link (dest_vectors->strokes));
+
+ dest_vectors->last_stroke_ID++;
+ gimp_stroke_set_ID (newstroke,
+ dest_vectors->last_stroke_ID);
+ }
+
+ gimp_vectors_thaw (dest_vectors);
+}
+
+
+void
+gimp_vectors_stroke_add (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ gimp_vectors_freeze (vectors);
+
+ GIMP_VECTORS_GET_CLASS (vectors)->stroke_add (vectors, stroke);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_real_stroke_add (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ /*
+ * Don't prepend into vector->strokes. See ChangeLog 2003-05-21
+ * --Mitch
+ */
+ g_queue_push_tail (vectors->strokes, g_object_ref (stroke));
+
+ /* Also add to {stroke: GList node} map */
+ g_hash_table_insert (vectors->stroke_to_list,
+ stroke,
+ g_queue_peek_tail_link (vectors->strokes));
+
+ vectors->last_stroke_ID++;
+ gimp_stroke_set_ID (stroke, vectors->last_stroke_ID);
+}
+
+void
+gimp_vectors_stroke_remove (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ gimp_vectors_freeze (vectors);
+
+ GIMP_VECTORS_GET_CLASS (vectors)->stroke_remove (vectors, stroke);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_real_stroke_remove (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ GList *list = g_hash_table_lookup (vectors->stroke_to_list, stroke);
+
+ if (list)
+ {
+ g_queue_delete_link (vectors->strokes, list);
+ g_hash_table_remove (vectors->stroke_to_list, stroke);
+ g_object_unref (stroke);
+ }
+}
+
+gint
+gimp_vectors_get_n_strokes (GimpVectors *vectors)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0);
+
+ return g_queue_get_length (vectors->strokes);
+}
+
+
+GimpStroke *
+gimp_vectors_stroke_get (GimpVectors *vectors,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get (vectors, coord);
+}
+
+static GimpStroke *
+gimp_vectors_real_stroke_get (GimpVectors *vectors,
+ const GimpCoords *coord)
+{
+ GimpStroke *minstroke = NULL;
+ gdouble mindist = G_MAXDOUBLE;
+ GList *list;
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+ GimpAnchor *anchor = gimp_stroke_anchor_get (stroke, coord);
+
+ if (anchor)
+ {
+ gdouble dx = coord->x - anchor->position.x;
+ gdouble dy = coord->y - anchor->position.y;
+
+ if (mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ minstroke = stroke;
+ }
+ }
+ }
+
+ return minstroke;
+}
+
+GimpStroke *
+gimp_vectors_stroke_get_by_ID (GimpVectors *vectors,
+ gint id)
+{
+ GList *list;
+
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ if (gimp_stroke_get_ID (list->data) == id)
+ return list->data;
+ }
+
+ return NULL;
+}
+
+
+GimpStroke *
+gimp_vectors_stroke_get_next (GimpVectors *vectors,
+ GimpStroke *prev)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get_next (vectors, prev);
+}
+
+static GimpStroke *
+gimp_vectors_real_stroke_get_next (GimpVectors *vectors,
+ GimpStroke *prev)
+{
+ if (! prev)
+ {
+ return g_queue_peek_head (vectors->strokes);
+ }
+ else
+ {
+ GList *stroke = g_hash_table_lookup (vectors->stroke_to_list, prev);
+
+ g_return_val_if_fail (stroke != NULL, NULL);
+
+ return stroke->next ? stroke->next->data : NULL;
+ }
+}
+
+
+gdouble
+gimp_vectors_stroke_get_length (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0);
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get_length (vectors, stroke);
+}
+
+static gdouble
+gimp_vectors_real_stroke_get_length (GimpVectors *vectors,
+ GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0);
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return gimp_stroke_get_length (stroke, vectors->precision);
+}
+
+
+GimpAnchor *
+gimp_vectors_anchor_get (GimpVectors *vectors,
+ const GimpCoords *coord,
+ GimpStroke **ret_stroke)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->anchor_get (vectors, coord,
+ ret_stroke);
+}
+
+static GimpAnchor *
+gimp_vectors_real_anchor_get (GimpVectors *vectors,
+ const GimpCoords *coord,
+ GimpStroke **ret_stroke)
+{
+ GimpAnchor *minanchor = NULL;
+ gdouble mindist = -1;
+ GList *list;
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+ GimpAnchor *anchor = gimp_stroke_anchor_get (stroke, coord);
+
+ if (anchor)
+ {
+ gdouble dx = coord->x - anchor->position.x;
+ gdouble dy = coord->y - anchor->position.y;
+
+ if (mindist > dx * dx + dy * dy || mindist < 0)
+ {
+ mindist = dx * dx + dy * dy;
+ minanchor = anchor;
+
+ if (ret_stroke)
+ *ret_stroke = stroke;
+ }
+ }
+ }
+
+ return minanchor;
+}
+
+
+void
+gimp_vectors_anchor_delete (GimpVectors *vectors,
+ GimpAnchor *anchor)
+{
+ g_return_if_fail (GIMP_IS_VECTORS (vectors));
+ g_return_if_fail (anchor != NULL);
+
+ GIMP_VECTORS_GET_CLASS (vectors)->anchor_delete (vectors, anchor);
+}
+
+static void
+gimp_vectors_real_anchor_delete (GimpVectors *vectors,
+ GimpAnchor *anchor)
+{
+}
+
+
+void
+gimp_vectors_anchor_select (GimpVectors *vectors,
+ GimpStroke *target_stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive)
+{
+ GList *list;
+
+ for (list = vectors->strokes->head; list; list = g_list_next (list))
+ {
+ GimpStroke *stroke = list->data;
+
+ gimp_stroke_anchor_select (stroke,
+ stroke == target_stroke ? anchor : NULL,
+ selected, exclusive);
+ }
+}
+
+
+gdouble
+gimp_vectors_get_length (GimpVectors *vectors,
+ const GimpAnchor *start)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->get_length (vectors, start);
+}
+
+static gdouble
+gimp_vectors_real_get_length (GimpVectors *vectors,
+ const GimpAnchor *start)
+{
+ g_printerr ("gimp_vectors_get_length: default implementation\n");
+
+ return 0;
+}
+
+
+gdouble
+gimp_vectors_get_distance (GimpVectors *vectors,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->get_distance (vectors, coord);
+}
+
+static gdouble
+gimp_vectors_real_get_distance (GimpVectors *vectors,
+ const GimpCoords *coord)
+{
+ g_printerr ("gimp_vectors_get_distance: default implementation\n");
+
+ return 0;
+}
+
+gint
+gimp_vectors_interpolate (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble precision,
+ gint max_points,
+ GimpCoords *ret_coords)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0);
+
+ return GIMP_VECTORS_GET_CLASS (vectors)->interpolate (vectors, stroke,
+ precision, max_points,
+ ret_coords);
+}
+
+static gint
+gimp_vectors_real_interpolate (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble precision,
+ gint max_points,
+ GimpCoords *ret_coords)
+{
+ g_printerr ("gimp_vectors_interpolate: default implementation\n");
+
+ return 0;
+}
+
+const GimpBezierDesc *
+gimp_vectors_get_bezier (GimpVectors *vectors)
+{
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL);
+
+ if (! vectors->bezier_desc)
+ {
+ vectors->bezier_desc = gimp_vectors_make_bezier (vectors);
+ }
+
+ return vectors->bezier_desc;
+}
+
+static GimpBezierDesc *
+gimp_vectors_make_bezier (GimpVectors *vectors)
+{
+ return GIMP_VECTORS_GET_CLASS (vectors)->make_bezier (vectors);
+}
+
+static GimpBezierDesc *
+gimp_vectors_real_make_bezier (GimpVectors *vectors)
+{
+ GimpStroke *stroke;
+ GArray *cmd_array;
+ GimpBezierDesc *ret_bezdesc = NULL;
+
+ cmd_array = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t));
+
+ for (stroke = gimp_vectors_stroke_get_next (vectors, NULL);
+ stroke;
+ stroke = gimp_vectors_stroke_get_next (vectors, stroke))
+ {
+ GimpBezierDesc *bezdesc = gimp_stroke_make_bezier (stroke);
+
+ if (bezdesc)
+ {
+ cmd_array = g_array_append_vals (cmd_array, bezdesc->data,
+ bezdesc->num_data);
+ gimp_bezier_desc_free (bezdesc);
+ }
+ }
+
+ if (cmd_array->len > 0)
+ ret_bezdesc = gimp_bezier_desc_new ((cairo_path_data_t *) cmd_array->data,
+ cmd_array->len);
+
+ g_array_free (cmd_array, FALSE);
+
+ return ret_bezdesc;
+}
diff --git a/app/vectors/gimpvectors.h b/app/vectors/gimpvectors.h
new file mode 100644
index 0000000..a0d8a64
--- /dev/null
+++ b/app/vectors/gimpvectors.h
@@ -0,0 +1,184 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpvectors.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_H__
+#define __GIMP_VECTORS_H__
+
+#include "core/gimpitem.h"
+
+#define GIMP_TYPE_VECTORS (gimp_vectors_get_type ())
+#define GIMP_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS, GimpVectors))
+#define GIMP_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS, GimpVectorsClass))
+#define GIMP_IS_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS))
+#define GIMP_IS_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS))
+#define GIMP_VECTORS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS, GimpVectorsClass))
+
+
+typedef struct _GimpVectorsClass GimpVectorsClass;
+
+struct _GimpVectors
+{
+ GimpItem parent_instance;
+
+ GQueue *strokes; /* Queue of GimpStrokes */
+ GHashTable *stroke_to_list; /* Map from GimpStroke to strokes listnode */
+ gint last_stroke_ID;
+
+ gint freeze_count;
+ gdouble precision;
+
+ GimpBezierDesc *bezier_desc; /* Cached bezier representation */
+
+ gboolean bounds_valid; /* Cached bounding box */
+ gboolean bounds_empty;
+ gdouble bounds_x1;
+ gdouble bounds_y1;
+ gdouble bounds_x2;
+ gdouble bounds_y2;
+};
+
+struct _GimpVectorsClass
+{
+ GimpItemClass parent_class;
+
+ /* signals */
+ void (* freeze) (GimpVectors *vectors);
+ void (* thaw) (GimpVectors *vectors);
+
+ /* virtual functions */
+ void (* stroke_add) (GimpVectors *vectors,
+ GimpStroke *stroke);
+ void (* stroke_remove) (GimpVectors *vectors,
+ GimpStroke *stroke);
+ GimpStroke * (* stroke_get) (GimpVectors *vectors,
+ const GimpCoords *coord);
+ GimpStroke * (* stroke_get_next) (GimpVectors *vectors,
+ GimpStroke *prev);
+ gdouble (* stroke_get_length) (GimpVectors *vectors,
+ GimpStroke *stroke);
+ GimpAnchor * (* anchor_get) (GimpVectors *vectors,
+ const GimpCoords *coord,
+ GimpStroke **ret_stroke);
+ void (* anchor_delete) (GimpVectors *vectors,
+ GimpAnchor *anchor);
+ gdouble (* get_length) (GimpVectors *vectors,
+ const GimpAnchor *start);
+ gdouble (* get_distance) (GimpVectors *vectors,
+ const GimpCoords *coord);
+ gint (* interpolate) (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble precision,
+ gint max_points,
+ GimpCoords *ret_coords);
+ GimpBezierDesc * (* make_bezier) (GimpVectors *vectors);
+};
+
+
+/* vectors utility functions */
+
+GType gimp_vectors_get_type (void) G_GNUC_CONST;
+
+GimpVectors * gimp_vectors_new (GimpImage *image,
+ const gchar *name);
+
+GimpVectors * gimp_vectors_get_parent (GimpVectors *vectors);
+
+void gimp_vectors_freeze (GimpVectors *vectors);
+void gimp_vectors_thaw (GimpVectors *vectors);
+
+void gimp_vectors_copy_strokes (GimpVectors *src_vectors,
+ GimpVectors *dest_vectors);
+void gimp_vectors_add_strokes (GimpVectors *src_vectors,
+ GimpVectors *dest_vectors);
+
+
+/* accessing / modifying the anchors */
+
+GimpAnchor * gimp_vectors_anchor_get (GimpVectors *vectors,
+ const GimpCoords *coord,
+ GimpStroke **ret_stroke);
+
+/* prev == NULL: "first" anchor */
+GimpAnchor * gimp_vectors_anchor_get_next (GimpVectors *vectors,
+ const GimpAnchor *prev);
+
+/* type will be an xorable enum:
+ * VECTORS_NONE, VECTORS_FIX_ANGLE, VECTORS_FIX_RATIO, VECTORS_RESTRICT_ANGLE
+ * or so.
+ */
+void gimp_vectors_anchor_move_relative (GimpVectors *vectors,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ gint type);
+void gimp_vectors_anchor_move_absolute (GimpVectors *vectors,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ gint type);
+
+void gimp_vectors_anchor_delete (GimpVectors *vectors,
+ GimpAnchor *anchor);
+
+void gimp_vectors_anchor_select (GimpVectors *vectors,
+ GimpStroke *target_stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive);
+
+
+/* GimpStroke is a connected component of a GimpVectors object */
+
+void gimp_vectors_stroke_add (GimpVectors *vectors,
+ GimpStroke *stroke);
+void gimp_vectors_stroke_remove (GimpVectors *vectors,
+ GimpStroke *stroke);
+gint gimp_vectors_get_n_strokes (GimpVectors *vectors);
+GimpStroke * gimp_vectors_stroke_get (GimpVectors *vectors,
+ const GimpCoords *coord);
+GimpStroke * gimp_vectors_stroke_get_by_ID (GimpVectors *vectors,
+ gint id);
+
+/* prev == NULL: "first" stroke */
+GimpStroke * gimp_vectors_stroke_get_next (GimpVectors *vectors,
+ GimpStroke *prev);
+gdouble gimp_vectors_stroke_get_length (GimpVectors *vectors,
+ GimpStroke *stroke);
+
+/* accessing the shape of the curve */
+
+gdouble gimp_vectors_get_length (GimpVectors *vectors,
+ const GimpAnchor *start);
+gdouble gimp_vectors_get_distance (GimpVectors *vectors,
+ const GimpCoords *coord);
+
+/* returns the number of valid coordinates */
+
+gint gimp_vectors_interpolate (GimpVectors *vectors,
+ GimpStroke *stroke,
+ gdouble precision,
+ gint max_points,
+ GimpCoords *ret_coords);
+
+/* usually overloaded */
+
+/* returns a bezier representation */
+const GimpBezierDesc * gimp_vectors_get_bezier (GimpVectors *vectors);
+
+
+#endif /* __GIMP_VECTORS_H__ */
diff --git a/app/vectors/gimpvectorsmodundo.c b/app/vectors/gimpvectorsmodundo.c
new file mode 100644
index 0000000..b681ba9
--- /dev/null
+++ b/app/vectors/gimpvectorsmodundo.c
@@ -0,0 +1,141 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "gimpvectors.h"
+#include "gimpvectorsmodundo.h"
+
+
+static void gimp_vectors_mod_undo_constructed (GObject *object);
+
+static gint64 gimp_vectors_mod_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_vectors_mod_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_vectors_mod_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpVectorsModUndo, gimp_vectors_mod_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_vectors_mod_undo_parent_class
+
+
+static void
+gimp_vectors_mod_undo_class_init (GimpVectorsModUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_vectors_mod_undo_constructed;
+
+ gimp_object_class->get_memsize = gimp_vectors_mod_undo_get_memsize;
+
+ undo_class->pop = gimp_vectors_mod_undo_pop;
+ undo_class->free = gimp_vectors_mod_undo_free;
+}
+
+static void
+gimp_vectors_mod_undo_init (GimpVectorsModUndo *undo)
+{
+}
+
+static void
+gimp_vectors_mod_undo_constructed (GObject *object)
+{
+ GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (object);
+ GimpVectors *vectors;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item));
+
+ vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (object)->item);
+
+ vectors_mod_undo->vectors =
+ GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors),
+ G_TYPE_FROM_INSTANCE (vectors)));
+}
+
+static gint64
+gimp_vectors_mod_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (vectors_mod_undo->vectors),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_vectors_mod_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (undo);
+ GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item);
+ GimpVectors *temp;
+ gint offset_x;
+ gint offset_y;
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ temp = vectors_mod_undo->vectors;
+
+ vectors_mod_undo->vectors =
+ GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors),
+ G_TYPE_FROM_INSTANCE (vectors)));
+
+ gimp_vectors_freeze (vectors);
+
+ gimp_vectors_copy_strokes (temp, vectors);
+
+ gimp_item_get_offset (GIMP_ITEM (temp), &offset_x, &offset_y);
+ gimp_item_set_offset (GIMP_ITEM (vectors), offset_x, offset_y);
+
+ gimp_item_set_size (GIMP_ITEM (vectors),
+ gimp_item_get_width (GIMP_ITEM (temp)),
+ gimp_item_get_height (GIMP_ITEM (temp)));
+
+ g_object_unref (temp);
+
+ gimp_vectors_thaw (vectors);
+}
+
+static void
+gimp_vectors_mod_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (undo);
+
+ g_clear_object (&vectors_mod_undo->vectors);
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/vectors/gimpvectorsmodundo.h b/app/vectors/gimpvectorsmodundo.h
new file mode 100644
index 0000000..e63a0ef
--- /dev/null
+++ b/app/vectors/gimpvectorsmodundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_MOD_UNDO_H__
+#define __GIMP_VECTORS_MOD_UNDO_H__
+
+
+#include "core/gimpitemundo.h"
+
+
+#define GIMP_TYPE_VECTORS_MOD_UNDO (gimp_vectors_mod_undo_get_type ())
+#define GIMP_VECTORS_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndo))
+#define GIMP_VECTORS_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndoClass))
+#define GIMP_IS_VECTORS_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_MOD_UNDO))
+#define GIMP_IS_VECTORS_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_MOD_UNDO))
+#define GIMP_VECTORS_MOD_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndoClass))
+
+
+typedef struct _GimpVectorsModUndo GimpVectorsModUndo;
+typedef struct _GimpVectorsModUndoClass GimpVectorsModUndoClass;
+
+struct _GimpVectorsModUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpVectors *vectors;
+};
+
+struct _GimpVectorsModUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_vectors_mod_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_VECTORS_MOD_UNDO_H__ */
diff --git a/app/vectors/gimpvectorspropundo.c b/app/vectors/gimpvectorspropundo.c
new file mode 100644
index 0000000..1a093df
--- /dev/null
+++ b/app/vectors/gimpvectorspropundo.c
@@ -0,0 +1,94 @@
+/* Gimp - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpvectors.h"
+#include "gimpvectorspropundo.h"
+
+
+static void gimp_vectors_prop_undo_constructed (GObject *object);
+
+static void gimp_vectors_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpVectorsPropUndo, gimp_vectors_prop_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_vectors_prop_undo_parent_class
+
+
+static void
+gimp_vectors_prop_undo_class_init (GimpVectorsPropUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_vectors_prop_undo_constructed;
+
+ undo_class->pop = gimp_vectors_prop_undo_pop;
+}
+
+static void
+gimp_vectors_prop_undo_init (GimpVectorsPropUndo *undo)
+{
+}
+
+static void
+gimp_vectors_prop_undo_constructed (GObject *object)
+{
+ /* GimpVectors *vectors; */
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item));
+
+ /* vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (object)->item); */
+
+ switch (GIMP_UNDO (object)->undo_type)
+ {
+ default:
+ gimp_assert_not_reached ();
+ }
+}
+
+static void
+gimp_vectors_prop_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+#if 0
+ GimpVectorsPropUndo *vectors_prop_undo = GIMP_VECTORS_PROP_UNDO (undo);
+ GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item);
+#endif
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ switch (undo->undo_type)
+ {
+ default:
+ gimp_assert_not_reached ();
+ }
+}
diff --git a/app/vectors/gimpvectorspropundo.h b/app/vectors/gimpvectorspropundo.h
new file mode 100644
index 0000000..8909226
--- /dev/null
+++ b/app/vectors/gimpvectorspropundo.h
@@ -0,0 +1,50 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_PROP_UNDO_H__
+#define __GIMP_VECTORS_PROP_UNDO_H__
+
+
+#include "core/gimpitemundo.h"
+
+
+#define GIMP_TYPE_VECTORS_PROP_UNDO (gimp_vectors_prop_undo_get_type ())
+#define GIMP_VECTORS_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndo))
+#define GIMP_VECTORS_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndoClass))
+#define GIMP_IS_VECTORS_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_PROP_UNDO))
+#define GIMP_IS_VECTORS_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_PROP_UNDO))
+#define GIMP_VECTORS_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndoClass))
+
+
+typedef struct _GimpVectorsPropUndo GimpVectorsPropUndo;
+typedef struct _GimpVectorsPropUndoClass GimpVectorsPropUndoClass;
+
+struct _GimpVectorsPropUndo
+{
+ GimpItemUndo parent_instance;
+};
+
+struct _GimpVectorsPropUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_vectors_prop_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_VECTORS_PROP_UNDO_H__ */
diff --git a/app/vectors/gimpvectorsundo.c b/app/vectors/gimpvectorsundo.c
new file mode 100644
index 0000000..646869f
--- /dev/null
+++ b/app/vectors/gimpvectorsundo.c
@@ -0,0 +1,213 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "vectors-types.h"
+
+#include "core/gimpimage.h"
+
+#include "gimpvectors.h"
+#include "gimpvectorsundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PREV_PARENT,
+ PROP_PREV_POSITION,
+ PROP_PREV_VECTORS
+};
+
+
+static void gimp_vectors_undo_constructed (GObject *object);
+static void gimp_vectors_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_vectors_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_vectors_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static void gimp_vectors_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+
+
+G_DEFINE_TYPE (GimpVectorsUndo, gimp_vectors_undo, GIMP_TYPE_ITEM_UNDO)
+
+#define parent_class gimp_vectors_undo_parent_class
+
+
+static void
+gimp_vectors_undo_class_init (GimpVectorsUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_vectors_undo_constructed;
+ object_class->set_property = gimp_vectors_undo_set_property;
+ object_class->get_property = gimp_vectors_undo_get_property;
+
+ gimp_object_class->get_memsize = gimp_vectors_undo_get_memsize;
+
+ undo_class->pop = gimp_vectors_undo_pop;
+
+ g_object_class_install_property (object_class, PROP_PREV_PARENT,
+ g_param_spec_object ("prev-parent",
+ NULL, NULL,
+ GIMP_TYPE_VECTORS,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_POSITION,
+ g_param_spec_int ("prev-position", NULL, NULL,
+ 0, G_MAXINT, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PREV_VECTORS,
+ g_param_spec_object ("prev-vectors", NULL, NULL,
+ GIMP_TYPE_VECTORS,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_vectors_undo_init (GimpVectorsUndo *undo)
+{
+}
+
+static void
+gimp_vectors_undo_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item));
+}
+
+static void
+gimp_vectors_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ vectors_undo->prev_parent = g_value_get_object (value);
+ break;
+ case PROP_PREV_POSITION:
+ vectors_undo->prev_position = g_value_get_int (value);
+ break;
+ case PROP_PREV_VECTORS:
+ vectors_undo->prev_vectors = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_vectors_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PREV_PARENT:
+ g_value_set_object (value, vectors_undo->prev_parent);
+ break;
+ case PROP_PREV_POSITION:
+ g_value_set_int (value, vectors_undo->prev_position);
+ break;
+ case PROP_PREV_VECTORS:
+ g_value_set_object (value, vectors_undo->prev_vectors);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_vectors_undo_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object);
+ gint64 memsize = 0;
+
+ if (! gimp_item_is_attached (item_undo->item))
+ memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item),
+ gui_size);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static void
+gimp_vectors_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (undo);
+ GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if ((undo_mode == GIMP_UNDO_MODE_UNDO &&
+ undo->undo_type == GIMP_UNDO_VECTORS_ADD) ||
+ (undo_mode == GIMP_UNDO_MODE_REDO &&
+ undo->undo_type == GIMP_UNDO_VECTORS_REMOVE))
+ {
+ /* remove vectors */
+
+ /* record the current parent and position */
+ vectors_undo->prev_parent = gimp_vectors_get_parent (vectors);
+ vectors_undo->prev_position = gimp_item_get_index (GIMP_ITEM (vectors));
+
+ gimp_image_remove_vectors (undo->image, vectors, FALSE,
+ vectors_undo->prev_vectors);
+ }
+ else
+ {
+ /* restore vectors */
+
+ /* record the active vectors */
+ vectors_undo->prev_vectors = gimp_image_get_active_vectors (undo->image);
+
+ gimp_image_add_vectors (undo->image, vectors,
+ vectors_undo->prev_parent,
+ vectors_undo->prev_position, FALSE);
+ }
+}
diff --git a/app/vectors/gimpvectorsundo.h b/app/vectors/gimpvectorsundo.h
new file mode 100644
index 0000000..9b54346
--- /dev/null
+++ b/app/vectors/gimpvectorsundo.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_VECTORS_UNDO_H__
+#define __GIMP_VECTORS_UNDO_H__
+
+
+#include "core/gimpitemundo.h"
+
+
+#define GIMP_TYPE_VECTORS_UNDO (gimp_vectors_undo_get_type ())
+#define GIMP_VECTORS_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndo))
+#define GIMP_VECTORS_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndoClass))
+#define GIMP_IS_VECTORS_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_UNDO))
+#define GIMP_IS_VECTORS_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_UNDO))
+#define GIMP_VECTORS_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndoClass))
+
+
+typedef struct _GimpVectorsUndo GimpVectorsUndo;
+typedef struct _GimpVectorsUndoClass GimpVectorsUndoClass;
+
+struct _GimpVectorsUndo
+{
+ GimpItemUndo parent_instance;
+
+ GimpVectors *prev_parent;
+ gint prev_position; /* former position in list */
+ GimpVectors *prev_vectors; /* previous active vectors */
+};
+
+struct _GimpVectorsUndoClass
+{
+ GimpItemUndoClass parent_class;
+};
+
+
+GType gimp_vectors_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_VECTORS_UNDO_H__ */
diff --git a/app/vectors/vectors-enums.h b/app/vectors/vectors-enums.h
new file mode 100644
index 0000000..c0c603b
--- /dev/null
+++ b/app/vectors/vectors-enums.h
@@ -0,0 +1,46 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * vectors-enums.h
+ * Copyright (C) 2006 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VECTORS_ENUMS_H__
+#define __VECTORS_ENUMS_H__
+
+
+typedef enum
+{
+ GIMP_ANCHOR_ANCHOR,
+ GIMP_ANCHOR_CONTROL
+} GimpAnchorType;
+
+typedef enum
+{
+ GIMP_ANCHOR_FEATURE_NONE,
+ GIMP_ANCHOR_FEATURE_EDGE,
+ GIMP_ANCHOR_FEATURE_ALIGNED,
+ GIMP_ANCHOR_FEATURE_SYMMETRIC
+} GimpAnchorFeatureType;
+
+typedef enum
+{
+ EXTEND_SIMPLE,
+ EXTEND_EDITABLE
+} GimpVectorExtendMode;
+
+
+#endif /* __VECTORS_ENUMS_H__ */
diff --git a/app/vectors/vectors-types.h b/app/vectors/vectors-types.h
new file mode 100644
index 0000000..7c0d2d3
--- /dev/null
+++ b/app/vectors/vectors-types.h
@@ -0,0 +1,37 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * vectors-types.h
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VECTORS_TYPES_H__
+#define __VECTORS_TYPES_H__
+
+
+#include "core/core-types.h"
+
+#include "vectors/vectors-enums.h"
+
+
+typedef struct _GimpAnchor GimpAnchor;
+
+typedef struct _GimpVectors GimpVectors;
+typedef struct _GimpStroke GimpStroke;
+typedef struct _GimpBezierStroke GimpBezierStroke;
+
+
+#endif /* __VECTORS_TYPES_H__ */