diff --git a/autogen.sh b/autogen.sh index 0cb2f1ad..d0539a1e 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,60 +1,60 @@ #!/bin/sh # # Run this to generate the configuration scripts after a fresh # repository clone/checkout. # # USAGE: ./autogen.sh [--verbose] # # where --verbose shows stdout and stderr of every command (by # default they are redirect to /dev/null). # # This script does *not* call configure (as usually done in other # projects) because this would prevent VPATH builds. arg1=$1 step() { printf "$1... " if test "$arg1" = --verbose; then eval $2 else eval $2 >/dev/null 2>&1 fi result=$? if test "$result" = "0"; then printf "\033[32mok\033[0m\n" else printf "\033[31mfailed\033[0m\n ** \"$2\" returned $result\n" exit $result fi } srcdir=`dirname $0` test -z "$srcdir" && srcdir=. step "Checking for top-level adg directory" \ "test -f '$srcdir/configure.ac' -a -f '$srcdir/src/adg.h.in'" cd "$srcdir" step "Creating dummy ChangeLog, if needed" \ "test -f './ChangeLog' || touch './ChangeLog'" # autoreconf interaction with libtool has been broken for ages: -# explicitely calling libtoolize seems to avoid some problem +# explicitly calling libtoolize seems to avoid some problem step "Calling libtoolize" \ "libtoolize --automake" # GNU gettext seems to have timestamp problems with git: # https://bugzilla.gnome.org/show_bug.cgi?id=661128 step "Making adg.pot look older" \ "touch -t 200001010000 po/adg.pot" step "Regenerating autotools files" \ "autoreconf -isf -Wall" printf "Now run \033[1m./configure\033[0m to customize your building\n" diff --git a/configure.ac b/configure.ac index 40e3169e..8410a86f 100644 --- a/configure.ac +++ b/configure.ac @@ -1,474 +1,474 @@ m4_define([adg_version],[0.9.4]) m4_define([cpml_version],adg_version) dnl How to handle {adg,cmpl}_lt_version (current:revision:age): dnl - ABI unmodified (e.g. refactoring): current:revision+1:age dnl - Backward compatible ABI (e.g. new API): current+1:0:age+1 dnl - Backward incompatible ABI: current+1:0:0 m4_define([adg_lt_version],[9:0:1]) m4_define([cpml_lt_version],[4:3:0]) m4_define([gtkdoc_prereq], [1.12] )dnl Support introspection annotations m4_define([gobject_prereq], [2.38.0])dnl Required by G_ADD_PRIVATE m4_define([cairo_prereq], [1.7.4] )dnl Required by cairo_scaled_font_text_to_glyphs() m4_define([gtk2_prereq], [2.18.0])dnl Required by gtk_widget_get_allocation() m4_define([gtk3_prereq], [3.0.0] )dnl First stable release m4_define([pangocairo_prereq],[1.10.0])dnl Cairo support in Pango m4_define([gi_prereq], [1.0.0] )dnl First stable release # Initialization dnl autoconf 2.62 is required by GObject introspection: dnl http://live.gnome.org/GObjectIntrospection/AutotoolsIntegration AC_PREREQ([2.62]) AC_INIT([adg-1],adg_version,[http://dev.entidi.com/p/adg/],[adg],[http://adg.entidi.com/]) AC_CONFIG_SRCDIR([configure.ac]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_AUX_DIR([autotools]) AC_CONFIG_MACRO_DIR([autotools]) AM_INIT_AUTOMAKE([1.10 gnits no-dist-gzip dist-bzip2 -Wall -Wno-portability]) dnl m4 backward compatibility stuff m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])], [AC_SUBST([AM_V_GEN],[])]) m4_ifdef([AM_SUBST_NOTMAKE],[], [AC_DEFUN([AM_SUBST_NOTMAKE],[_AM_SUBST_NOTMAKE($@)])]) m4_ifdef([AM_COND_IF],[], [AC_DEFUN([AM_COND_IF], [AS_IF([test -z "$$1_TRUE"],[$2],[$3])])]) AC_DEFUN([ADG_DEPENDENCY], [EXTRADIR="$EXTRADIR --extra-dir=$($PKG_CONFIG --variable=prefix $1)/share/gtk-doc/html/$2"]) # Check for host information AC_CANONICAL_HOST case "${host}" in *-*-linux*) guessed_host=linux ;; *-*-freebsd*) guessed_host=freebsd ;; *-*-mingw*) guessed_host=mingw ;; *-*-solaris*) guessed_host=solaris ;; *-*-darwin*) guessed_host=darwin ;; *-*-beos*) guessed_host=beos ;; *-*-cygwin*) guessed_host=cygwin ;; *-*-minix*) guessed_host=minix ;; *-*-aix*) guessed_host=aix ;; *) guessed_host=unknown ;; esac AM_CONDITIONAL([OS_UNIX],[test "${guessed_host}" != "mingw"]) AM_CONDITIONAL([OS_WINDOWS],[test "${guessed_host}" = "mingw" -o "${guessed_host}" = "cygwin"]) # Check for programs AC_PROG_CC AC_PROG_SED PKG_PROG_PKG_CONFIG AC_PATH_PROG([XSLTPROC],[xsltproc],[/usr/bin/xsltproc]) AC_PATH_PROG([GLIB_MKENUMS],[glib-mkenums],[/usr/bin/glib-mkenums]) AC_PATH_PROG([GLIB_GENMARSHAL],[glib-genmarshal],[/usr/bin/glib-genmarshal]) AC_PATH_PROG([GTESTER],[gtester],[/usr/bin/gtester]) AC_PATH_PROG([GTESTER_REPORT],[gtester-report],[/usr/bin/gtester-report]) AC_PATH_PROG([GLADE3],[glade-3],[/usr/bin/glade-3]) AC_ARG_VAR([TAR]) AC_PATH_PROG([TAR],[tar],[/usr/bin/tar]) AC_ARG_VAR([BZIP2]) AC_PATH_PROG([BZIP2],[bzip2],[/usr/bin/bzip2]) AC_PATH_PROG([BEAR],[bear],[/usr/bin/bear]) # Check for coverage build dnl Coverage with gcov is available only for GNU gcc AS_IF([test "x$GNU" != "xyes"], [AC_ARG_ENABLE([gcov], [AS_HELP_STRING([--enable-gcov], [enable coverage build @<:@default=no@:>@])], [],[enable_gcov=no])]) AS_IF([test "x$enable_gcov" = "xyes"], [AC_ARG_VAR([GCOV]) AC_CHECK_TOOL([GCOV],[gcov],[/usr/bin/gcov]) CFLAGS="$CFLAGS --coverage" LDFLAGS="$LDFLAGS --coverage"]) AM_CONDITIONAL([ENABLE_GCOV],[test "x$enable_gcov" = "xyes"]) # Check for libraries AC_CHECK_LIB([m],[cos]) dnl On Windows, GLib 2.16 is required by _adg_dgettext() for the dnl g_win32_get_package_installation_directory_of_module() API. dnl On other platforms, it just needs to be in sync with GObject. AM_COND_IF([OS_WINDOWS], [AC_PATH_PROG([MAKENSIS],[makensis],[/usr/bin/makensis]) AC_PATH_PROG([WINE],[wine]) AC_ARG_VAR([WINDRES],[Command for Windows resources manipulation]) AC_CHECK_TOOL([WINDRES],[windres]) glib_prereq=2.16.0], [glib_prereq=]gobject_prereq) # Libtool initialization AC_MSG_CHECKING([which libtool initialization strategy to adopt]) AC_MSG_RESULT([m4_ifset([LT_INIT],[LT-INIT],[AC-PROG-LIBTOOL])]) m4_ifset([LT_INIT], [LT_INIT([disable-static win32-dll])], [AC_DISABLE_STATIC AC_LIBTOOL_WIN32_DLL AC_PROG_LIBTOOL]) # I18n dnl The following macro is required in order to be able to use dnl autopoint (formerly called gettextize) from autoreconf. dnl The argument is a trade-off between modern and supported. AM_GNU_GETTEXT_VERSION([0.17]) AM_GNU_GETTEXT([external]) AC_SUBST([ADG_API_PACKAGE],AC_PACKAGE_TARNAME) AC_SUBST([GETTEXT_PACKAGE],[${ADG_API_PACKAGE}]) AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["${ADG_API_PACKAGE}"], [Define to the domain used by gettext.]) # Check for required packages PKG_CHECK_MODULES([GLIB],[glib-2.0 >= ]${glib_prereq}, [glib_postfix=" (glib-$($PKG_CONFIG --modversion glib-2.0))"]) ADG_DEPENDENCY([glib-2.0],[glib]) PKG_CHECK_MODULES([GOBJECT],[gobject-2.0 >= ]gobject_prereq) ADG_DEPENDENCY([gobject-2.0],[gobject]) PKG_CHECK_MODULES([CAIRO],[cairo >= ]cairo_prereq) ADG_DEPENDENCY([cairo],[cairo]) # Check for optional packages dnl PangoCairo AC_ARG_ENABLE([pango], [AS_HELP_STRING([--enable-pango], [use pango for rendering text @<:@default=check@:>@])], [],[enable_pango=check]) AS_IF([test "x${enable_pango}" != "xno"], [PKG_CHECK_MODULES([PANGO],[pangocairo >= ]pangocairo_prereq, [enable_pango=yes AC_DEFINE_UNQUOTED([PANGO_ENABLED],[1], [Defined if the pango support is enabled.]) ADG_DEPENDENCY([pangocairo],[pango]) pango_postfix=" (pangocairo-$($PKG_CONFIG --modversion pangocairo))"], [AS_IF([test "x${enable_pango}" = "xyes"], [AC_MSG_ERROR([${PANGO_PKG_ERRORS} and pango support requested])], [enable_pango=no])])]) AM_CONDITIONAL([HAVE_PANGO],[test "x${enable_pango}" = "xyes"]) dnl GTK+ support AC_ARG_WITH(gtk, [AS_HELP_STRING([--with-gtk@<:@=gtk2/gtk3@:>@], [include GTK+ specific widgets @<:@default=check@:>@])], [],[with_gtk=check]) AS_CASE(["${with_gtk}"], [gtk2], [gtk2_pkg=yes; gtk3_pkg=no], [gtk3], [gtk2_pkg=no; gtk3_pkg=yes], [check|yes], [gtk2_pkg=yes; gtk3_pkg=yes], [no], [gtk2_pkg=no; gtk3_pkg=no], [AC_MSG_ERROR([Invalid option passed to --with-gtk])]) # GTK+ autodetection, giving precedence to GTK+3 AS_IF([test "x${gtk3_pkg}" = "xyes"], [PKG_CHECK_MODULES([GTK3],[gtk+-3.0 >= ]gtk3_prereq, [gtk_pkg=gtk3 ADG_DEPENDENCY([gtk+-3.0],[gtk3])], [:])]) AS_IF([test "x${gtk2_pkg}" = "xyes" -a "x${gtk_pkg}" = "x"], [PKG_CHECK_MODULES([GTK2],[gtk+-2.0 >= ]gtk2_prereq, [gtk_pkg=gtk2 ADG_DEPENDENCY([gtk+-2.0],[gtk2])], [:])]) AS_IF([test "x${gtk_pkg}" != "x"],[with_gtk=${gtk_pkg}]) # Handle "check" graceful degradation AS_IF([test "x${with_gtk}" = "xcheck"],[with_gtk=no]) # Handle failures AS_IF([test "x${with_gtk}" != "xno" -a "x${gtk_pkg}" = "x"], - [AC_MSG_ERROR([GTK+ support explicitely required but no valid GTK+ found])]) + [AC_MSG_ERROR([GTK+ support explicitly required but no valid GTK+ found])]) # 3.10.8 is the last GTK3 version known to run tests properly AS_IF([test "x${with_gtk}" = "xgtk3"], [gtk_postfix=" (gtk+-$($PKG_CONFIG --modversion gtk+-3.0))" PKG_CHECK_EXISTS([gtk+-3.0 > 3.10.8], [disable_gtk3_tests=yes]) AC_DEFINE_UNQUOTED([GTK3_ENABLED],[1], [Defined if the GTK+3 support is enabled.])]) AS_IF([test "x${with_gtk}" = "xgtk2"], [gtk_postfix=" (gtk+-$($PKG_CONFIG --modversion gtk+-2.0))" AC_DEFINE_UNQUOTED([GTK2_ENABLED],[1], [Defined if the GTK+2 support is enabled.])]) AM_CONDITIONAL([HAVE_GTK], [test "x${with_gtk}" != "xno"]) AM_CONDITIONAL([HAVE_GTK2],[test "x${with_gtk}" = "xgtk2"]) AM_CONDITIONAL([HAVE_GTK3],[test "x${with_gtk}" = "xgtk3"]) AM_CONDITIONAL([DISABLE_GTK3_TESTS],[test "x${disable_gtk3_tests}" = "xyes"]) dnl Glade catalog dir AC_ARG_WITH(glade_catalogdir, [AS_HELP_STRING([--with-glade-catalogdir@<:@=DIR@:>@], [where to install the glade catalogs @<:@default=check@:>@])], [],[with_glade_catalogdir=check]) # Both "check" and "yes" mean gladeui autodetection, # but "yes" will fail on gladeui not found AS_IF([test "x${with_glade_catalogdir}" = "xcheck" -o "x${with_glade_catalogdir}" = "xyes"], [glade_pkg=check]) # gladeui autodetection, giving precedence to gladeui-2.0 if possible AS_IF([test "x${glade_pkg}" = "xcheck" -a "x${with_gtk}" != "xgtk2"], [PKG_CHECK_MODULES([GLADEUI2],[gladeui-2.0],[glade_pkg=gladeui-2.0],[:])]) AS_IF([test "x${glade_pkg}" = "xcheck"], [PKG_CHECK_MODULES([GLADEUI1],[gladeui-1.0],[glade_pkg=gladeui-1.0],[:])]) AS_IF([test "x${glade_pkg}" != "x" -a "x${glade_pkg}" != "xcheck"], [NTD_UNEXPAND([${glade_pkg}], [catalogdir], [with_glade_catalogdir="${NTD_UNEXPANDED}"], [AC_MSG_ERROR([${glade_pkg} found but pkg-config file invalid])])]) # Handle "yes" failure AS_IF([test "x${glade_pkg}" = "xcheck" -a "x${with_glade_catalogdir}" = "xyes"], - [AC_MSG_ERROR([Glade support explicitely required but no valid gladeui found])]) + [AC_MSG_ERROR([Glade support explicitly required but no valid gladeui found])]) # Handle "check" graceful degradation AS_IF([test "x${glade_pkg}" = "xcheck" -a "x${with_glade_catalogdir}" = "xcheck"], [with_glade_catalogdir=no]) AM_CONDITIONAL([HAVE_GLADE],[test "x${with_glade_catalogdir}" != "xno"]) AM_COND_IF([HAVE_GLADE], [enable_glade=yes report_glade=" Glade catalog dir: ${with_glade_catalogdir}"], [enable_glade=no report_glade=""]) AC_SUBST([GLADE_CATALOGDIR],${with_glade_catalogdir}) dnl gtk-doc GTK_DOC_CHECK(gtkdoc_prereq) AS_IF([test "x$enable_gtk_doc" = "xyes"], [gtkdoc_postfix=" (gtk-doc-$($PKG_CONFIG --modversion gtk-doc))"]) dnl GObject introspection GOBJECT_INTROSPECTION_CHECK(gi_prereq) dnl If introspection is used, GObject support in cairo must be enabled. dnl This in turn means cairo-gobject library must be present. That dnl library has been introduced in cairo 1.10.0. AS_IF([test "x$enable_introspection" = "xyes"], [PKG_CHECK_MODULES([CAIRO_GOBJECT],[cairo-gobject], [AC_DEFINE_UNQUOTED([CAIRO_GOBJECT_ENABLED],[1], [Defined if GObject support in cairo is present.])], [AC_MSG_ERROR([GObject introspection requires the cairo-gobject library])])]) dnl Introspection girdir AC_ARG_WITH(girdir, [AS_HELP_STRING([--with-girdir@<:@=DIR@:>@], [where to install the .gir files @<:@default=check@:>@])], [],[with_girdir=check]) AS_IF([test "x$with_girdir" = "xcheck"], [with_girdir="$INTROSPECTION_GIRDIR"]) AC_SUBST([ADG_GIRDIR],${with_girdir}) dnl Introspection typelibdir AC_ARG_WITH(typelibdir, [AS_HELP_STRING([--with-typelibdir@<:@=DIR@:>@], [where to install the .typelib files @<:@default=check@:>@])], [],[with_typelibdir=check]) AS_IF([test "x$with_typelibdir" = "xcheck"], [with_typelibdir="$INTROSPECTION_TYPELIBDIR"]) AC_SUBST([ADG_TYPELIBDIR],${with_typelibdir}) dnl Introspection report AS_IF([test "x$enable_introspection" = "xyes"], [report_introspection=" Where to install gir files: $with_girdir Where to install the typelib files: $with_typelibdir"], [report_introspection=""]) dnl GLib test framework AC_ARG_ENABLE([test_framework], [AS_HELP_STRING([--enable-test-framework], [enable GLib test framework @<:@default=check@:>@])], [],[enable_test_framework=check]) AS_IF([test "x$enable_gcov" = "xyes"], [enable_test_framework=yes]) AS_IF([test "x${enable_test_framework}" != "xno"], [PKG_CHECK_EXISTS([glib-2.0 >= 2.16.0], [enable_test_framework=yes], [AS_IF([test "x${enable_test_framework}" = "xyes"], [AC_MSG_ERROR([The test framework needs glib2-2.16.0 or later])], [enable_test_framework=no])])]) AM_CONDITIONAL([HAVE_TEST_FRAMEWORK],[test "x${enable_test_framework}" = "xyes"]) # Check for compiler characteristics AC_C_CONST # Windows substitutions for NSis installer dnl Get the HOSTPREFIX from gobject-2.0.pc: all the dependencies *must* be dnl installed under the same prefix. HOSTPREFIX="$($PKG_CONFIG --variable=prefix gobject-2.0)" AC_SUBST([HOSTPREFIX]) AM_SUBST_NOTMAKE([HOSTPREFIX]) dnl -win$PACKAGE_ARCH will be appended to the installer file name AS_IF([test "x${host_cpu}" = "xx86_64"],[PACKAGE_ARCH=64],[PACKAGE_ARCH=32]) AC_SUBST([PACKAGE_ARCH]) # Additional substitutions dnl The GObject dependency can be made optional by skipping dnl the building of the GObject wrappers. CPML_REQUIRES='cairo >= cairo_prereq gobject-2.0 >= gobject_prereq' AM_COND_IF([HAVE_PANGO], [ADG_REQUIRES='pangocairo >= pangocairo_prereq' ADG_H_ADDITIONAL=' #include #include "adg/adg-text.h" #include "adg/adg-pango-style.h" '], [ADG_REQUIRES='gobject-2.0 >= gobject_prereq' ADG_H_ADDITIONAL='']) AM_COND_IF([HAVE_GTK3],[ADG_REQUIRES='gtk+-3.0 >= gtk3_prereq']) AM_COND_IF([HAVE_GTK2],[ADG_REQUIRES='gtk+-2.0 >= gtk2_prereq']) AM_COND_IF([HAVE_GTK], [ADG_H_ADDITIONAL="${ADG_H_ADDITIONAL} #include #include \"adg/adg-gtk-utils.h\" #include \"adg/adg-gtk-area.h\" #include \"adg/adg-gtk-layout.h\" " ADG_CANVAS_H_ADDITIONAL=' #include void adg_canvas_set_paper (AdgCanvas *canvas, const gchar *paper_name, GtkPageOrientation orientation); void adg_canvas_set_page_setup (AdgCanvas *canvas, GtkPageSetup *page_setup); GtkPageSetup * adg_canvas_get_page_setup (AdgCanvas *canvas); '], [ADG_CANVAS_H_ADDITIONAL='']) AC_SUBST([CPML_LT_VERSION],cpml_lt_version) AC_SUBST([CPML_REQUIRES]) AC_SUBST([ADG_LT_VERSION],adg_lt_version) AC_SUBST([ADG_REQUIRES]) AC_SUBST([ADG_H_ADDITIONAL]) AC_SUBST([ADG_CANVAS_H_ADDITIONAL]) AM_SUBST_NOTMAKE([CPML_REQUIRES]) AM_SUBST_NOTMAKE([ADG_REQUIRES]) AM_SUBST_NOTMAKE([ADG_H_ADDITIONAL]) AM_SUBST_NOTMAKE([ADG_CANVAS_H_ADDITIONAL]) dnl CPML compiler flags and library dependencies CPML_CFLAGS="$CAIRO_CFLAGS $GOBJECT_CFLAGS" CPML_LIBS="$CAIRO_LIBS $GOBJECT_LIBS" AC_SUBST([CPML_CFLAGS]) AC_SUBST([CPML_LIBS]) dnl ADG compiler flags and library dependencies ADG_CFLAGS="$CAIRO_GOBJECT_CFLAGS" ADG_LIBS="$CAIRO_GOBJECT_LIBS" AM_COND_IF([HAVE_PANGO], [ADG_CFLAGS="$PANGO_CFLAGS $ADG_CFLAGS" ADG_LIBS="$PANGO_LIBS $ADG_LIBS"]) AM_COND_IF([HAVE_GTK2], [ADG_CFLAGS="$GTK2_CFLAGS $ADG_CFLAGS" ADG_LIBS="$GTK2_LIBS $ADG_LIBS"]) AM_COND_IF([HAVE_GTK3], [ADG_CFLAGS="$GTK3_CFLAGS $ADG_CFLAGS" ADG_LIBS="$GTK3_LIBS $ADG_LIBS"]) AC_SUBST([ADG_CFLAGS]) AC_SUBST([ADG_LIBS]) # Path to external dependencies, used by gtkdoc-fixxref AC_SUBST([EXTRADIR]) # On VPATH builds, adg-canvas.h is created in the builddir so it must be # manually included by gtkdoc. On in-place builds, this is not needed. AS_IF([test "x${srcdir}" = "x."], [ADG_CANVAS_H=], [ADG_CANVAS_H='$(top_builddir)/src/adg/adg-canvas.h']) AC_SUBST([ADG_CANVAS_H]) # Generation AC_CONFIG_FILES([autotools/Custom.nsh Makefile src/Makefile src/tests/Makefile src/cpml/cpml-1.pc src/cpml/Makefile src/cpml/tests/Makefile src/adg/adg-1.pc src/adg.h src/adg/Makefile src/adg/adg-canvas.h src/adg/tests/Makefile demo/Makefile demo/adg-demo.ui po/Makefile.in docs/Makefile docs/cpml/Makefile docs/cpml/bookinfo.xml docs/adg/Makefile docs/adg/bookinfo.xml]) AC_CONFIG_FILES([demo/adg-glade],[chmod +x demo/adg-glade]) AC_OUTPUT # Report AC_MSG_NOTICE([generating report AC_PACKAGE_NAME adg_version will be built with the following options: ---------------------------------------------------------- CPML library to use: internal (cpml-adg_version) Build pango based entities: ${enable_pango}${pango_postfix} GTK+ support: ${with_gtk}${gtk_postfix} Install glade catalogs: ${enable_glade}${report_glade} Build API reference: ${enable_gtk_doc}${gtkdoc_postfix} GObject instrospection: ${enable_introspection}${report_introspection} Test coverage build: ${enable_gcov} Test framework support: ${enable_test_framework}${glib_postfix} ]) diff --git a/docs/adg/NEWS.xml b/docs/adg/NEWS.xml index d3df2e2f..9561552f 100644 --- a/docs/adg/NEWS.xml +++ b/docs/adg/NEWS.xml @@ -1,666 +1,666 @@ News archive Summary of changes between releases ADG 0.9.4 Allow to build the ADG project with meson. This is a major feature that allows to modernize part of the codebase and speed up the building process. The legacy autotools code will be kept around for some time, but meson is the preferred one. Drop Travis code integration and switch to GitLab. Thanks for the fish Travis, but the new model is not that opensource friendly. New API (adg_canvas_export_data) that returns the data as a memory chunk instead of writing it to a file. This is a requirement for adg-openresty. Update documentation building to latest gtk-doc changes. Improve uninstalled detection at runtime: the adg-demo-uninstalled hack is no longer needed. Fix g_memdup vulnerability. ADG 0.9.3 Get rid of deprecated code, specifically G_TYPE_INSTANCE_GET_PRIVATE (deprecated since 2.58) and GParameter (deprecated since 2.54 because that type is not introspectable). Use g_object_new_with_properties where available and provide fallback code for older glib-object code. Fix a long standing bug on table cell desctruction, resulting in a core dump when trying to destroy cells with duplicate names. Fix test units. On recent GTK releases the code crashes when creating a widget without X server: just skip GTK tests when gtk > 3.10.8. Allow to build the compilation database, used by language servers used for example to add semantic features to editors (e.g.: coc-vim). Minor docs improvements. ADG 0.9.2 Allow negative values in spacing options so text can be squeezed in less space than line height (useful on e.g. limits). Proper bindings for AdgDash have been provided, so now line styles can be customized outside of the C world. Floating values are now accepted as scale arguments, so "4.2:1" or "2:3.2" are now perfectly valid scale strings. The private data handling has been modernized by leveraging the latest APIs provided by GLib. Private accessors and workarounds have been definitely removed. Because of this, glib-2.38.0 is now required. Many minor bugs have been squashed in this release. ADG 0.9.1 Entities can now be "floating". When an entity is floating (the default is not), its extents does not concur on increasing the extents of its own container. A new path method (adg_path_append_trail) allows to append an AdgTrail to an AdgPath, also importing the named pairs. Styles can be cloned with a single API call (adg_style_clone). This greatly simplifies the way one can customize the styles. Custom dimension dresses introduced in 0.9.0 have been dropped to favor custom styles. Working directly on the primitives of an AdgPath has been improved: some new APIs have been added on this regard (cpml_primitive_type, adg_trail_n_segments and adg_path_remove_primitive). The reflect operation of AdgPath has been heavily tested and improved: now you can reflect a multisegment path without issues. Similarly, the AdgEdge entity has been tested and improved and now can be applied on multisegment paths. A new API (adg_path_join) allows to join all the segments inside the path into a single segment. AdgDim now has a "rounding" property. Some annotation bugs have been fixed, so the bindings of those APIs now work correctly (adg_path_arc, cpml_primitive_dump, adg_path_append_cairo_path and adg_trail_put_segment). ADG 0.9.0 Three new dresses have been added to address different quote types: ADG_DRESS_DIMENSION_ANGULAR (that formats numbers in sexagesimal units, bound by default to AdgADim instances), ADG_DRESS_DIMENSION_RADIUS (that prefixes values with R, bound to AdgRDim instances) and ADG_DRESS_DIMENSION_DIAMETER (that prefixes the value with a diameter symbol). The text generation algorithm has been greatly improved. The AdgDimStyle object now has 3 specific properties dedicated to the formatting of quotes: "number-format", "number-arguments" and "decimals". This allows, among other things, to properly render sexagesimal angles. Some enum type has been converted to int on public APIs. enum variables cannot precede ellipsis arguments: the last non ellipsis argument will be promoted to int and if the enum is not the same size (it could happen!) bad things will happen. A backward compatibility bug that prevented out of the box building of the project on old platforms has been fixed. The testing provided by Travis-CI has been improved by building for GTK+2 and GTK+3 by using both gcc and clang. The Windows installer now includes Lua support. To be able to have that enabled by default, gobject introspection has been ported to Windows. ADG 0.8.0 The code shared by tests has been moved on an internal library (libadgtest) shared by both CPML and ADG. The test coverage is now checked by leveraging the Coveralls service provided by GitHub. The percentage has been raised from 53% to 94%. A fistful of bugs found while improving the test coverage has been corrected, most notably cpml_extents_is_inside(), cpml_segment_reverse() and some intersection algorithm. Children widgets now own weak references to parents, avoiding the circular dependency introduced by strong references. The overall sanity of the ADG library has been improved by adding additional checks where needed. The CPML library is not subject to those checks because by design it is not supposed to be sane. ADG 0.7.6 Page margins and paddings are now managed consistently across the different surfaces and their behavior has been documented. The NSIS script for generating the Windows installers has been subject to many improvements: it can now optionally install ADG and CPML manuals (either in HTML and in PDF format) and the localization data (only italian for now). It should also handle the application icons properly. The adg_canvas_export() function can save a canvas to a file in one command. It needs a patched gobject-introspection though: see bug #743364. The localization infrastructure has been improved up to the point gettextize and intltool are no more required. ADG 0.7.5 The algorithm to use for offsetting Bézier curves is now selectable by calling the non-reentrant cpml_curve_offset_algorithm() function. A new offsetting algorithm (BAIOCA) has been implemented. It tries to minimize the error between the offset curve and the ideal offset point on evenly spaced t values. The source code has been revamped to minimize the warnings raised by new versions of gcc, gtk-doc and gobject-introspection. The internal sources have been refactored to manage GTK+2 and GTK+3 differences with the same codebase. ADG 0.7.4 Improved documentation: the API reference manual is now kept in sync with the online documentation by using a dedicated SilverStripe module. This really simplifies the maintenance burden of the doc. A bug that was preventing the correct finalization of the returned cairo path from GObject introspection bindings has been corrected. ADG 0.7.3 The compatibility of the project with outdated systems has been improved in order to be able to install it on old web servers. Compilation without GTK+ support has been tested: the canvas is now properly working on a server without X server installed. ADG 0.7.2 Out of the box bindings are now fully working: the adg-demo program provided by adg-lua is in par with the official adg-demo in C. AdgDress is inherited from GEnum instead of being a handcrafted solution. A lot of specialized code have been removed, so the mapping between numbers and names in bindings is automatic. Consistency in widget names has been improved, allowing to simplify signal connections on the Lua side. ADG 0.7.1 Out of the box support for LGI (dynamic Lua bindings based on GObject introspection) is now provided upstream and effectively used for testing APIs. The introspection support has been improved, some APIs (AdgPoint related methods above all) have been protected against NULL and some bugs raised by the bindings work have been corrected. The GBoxed wrappers for cairo structs such as cairo_matrix_t have been dropped in favor of the native support provided by the cairo-gobject library. GObject wrappers for CPML structs such as pairs, primitives and segments has been moved to CPML itself. This makes redundant the presence of wrappers on the ADG side, simplyfing the work for the language bindings. ADG 0.7.0 The autoscaling feature is now available: a series of predefined scale factors are applied until the proper one is found. The demo program autoscales the drawing on a right click on the drawing area and restores the paper zoom and position on a left click. The introspection support has been improved, making it possible to have LGI (Lua) bindings working out of the box. The typedef hack has been dropped in favor of a private forwarder header. The dash pattern of AdgLineStyle can now be customized by binding to it an AdgDash instance. ADG 0.6.6 The project has been ported to GTK+3 also on Windows platforms. The helper scripts now build only GTK+3 installers. The recent porting of Fedora mingw packages to archlinux made possible to generate for the first time a win64 installer. adg-demo now embeds icons of different sizes, ranging from 16x16 to 128x128 pixels. The look-up falls back to srcdir so the icons can be used also without installing the program. A bunch of new APIs has been added to help this relocation. The Windows installers received a lot of cosmetic improvements. The NSIS code has been cleaned up. ADG 0.6.5 Some missing documentation files that prevented the proper build adg-0.6.4 have been included in the distribution package. ADG 0.6.4 The Lua bindings, based on lgob, are now available as a separate project (adg-lua). The glue code is automatically generated using GObject introspection. The build system can detect and use GTK+3 and gladeui-2.0 when found. GTK+2 and gladeui-1.0 are still availables and can be forced at configure time. The rendering of AdgRDim has been enhanced: when the "outside" property is enabled, the leader line will extend beyoud the arc to quote for a styleable distance, and the marker will be reveresed. The best text frontend is selected at configure time. When pango is available, it will be preferred over the cairo toy text APIs. ADG 0.6.3 The text manipulation logic has been moved inside the AdgTextual interface and a new entity based on pango (AdgText) has been added. Also AdgToyText implements AdgTextual so all the text entities are now interchangeable. The demo program has a new dialog page that allows to customize the title block of the drawing. The zoom hints have been moved outside the paper so the drawing can be considered finalized. The CPML library has its own test framework. Anyway tests have been improved generally so it is now possible to generate a report by using "make test-report". The missing extents computation on AdgRDim, AdgADim and AdgArrow are now in place. By calling adg-demo with the -E option it is possible to visually verify the extents status. A bunch of bugs, most notably the wrong glade catalogdir detection and some extents computation, have been resolved. ADG 0.6.2 The localization infrastructure has been added and tested by adding the italian translation. The AdgGtkArea widget is now capable of interactively change zoom and pan in global space by dragging and/or rotating the wheel while keeping the shift pressed. A new class has been implemented: AdgGtkLayout is an AdgGtkArea based widget that implements the scrolling natively, hence it can be added directly to a GtkScrolledWindow container. ADG 0.6.1 The canvas now has printing support when compiled with GTK+ enabled. The media setup can be associated to the canvas so this data can be accessed to provide a consistent behavior between different backends. The demo program can now render the drawing to an SVG file. Generated files are stored in the user document directory. Uncomplete entities now fail gracefully instead of complaining for insufficient data. This provides an easy way to implement optional machinings: the optional groove in the adg-demo program is working by leveraging this feature. ADG 0.6.0 The portability of the build system has been improved: the ADG project now builds out of the box on FreeBSD, OpenSolaris, MinGW32 and GNU/Linux. The adg-demo program has been rewritten almost from scratch to provide a nice example of what the ADG should be used for. The rendering has been cleaned up and the model-view interaction now works: changing the data on the edit dialog changes the drawing (this is what the ADG has been developed for). The GTK+ widget, although not shining, is fairly usable and light years ahead of the previous version. The canvas now has custom paddings and margins, a background dress and the ability to accept an AdgTitleBlock entity. The project tree has been rearranged in three different subprojects: CPML (mathematical stuff above cairo), ADG (the canvas above CPML) and ADG-GTK (user interface helpers above ADG and GTK+). Support for glade-3 has been added: if enabled, ADG-GTK will try to install its catalog in the proper glade directory. ADG 0.5.6 The build system has done a huge step toward portability: the requirements are less restrictive and the overall implementation is cleaner and well defined. The API has been exercised by API-Sanity-Autotest that helped to discover some problems: check out issue #23 for details. A test framework based on glib-2.16 has been added. It is still under development but yet fully working. The CPML API has been cleaned up by using an internal struct of function pointers to delegate the job to the different primitives. The public symbols of the CPML library have been reduced from 105 to 70. ADG 0.5.5 The quote of linear dimensions can be freely positioned using the AdgDim:pos property. By default, ADG keeps the quote inside the extension lines if there is enough room, otherwise displaces it to the "pos" coordinates. A document describing how to contribute to the project has been added. A general description of the API conventions used by is also provided. A lot of bug fixing and code clean up. A bug in the installation has been solved and support for pkg-config has been added. This will probably be the last release of the 0.5 branch. ADG 0.5.4 AdgTable now provides a generic tabular entity customizable with AdgTableStyle. It supports unlimited rows with independent cells. Some new special entities, such as AdgLogo (the official ADG logo) and AdgProjection (showing the drawing projection scheme) have been implemented. A bare title block entity, AdgTitleBlock, is now available: it is allowed to set its cells content either by using string values or custom entities. AdgAlignment is a new container entity that allows to traslate its children of a custom factor of the children boundary box: any entity can now be right aligned or centered. ADG 0.5.3 AdgRDim, a new entity to quote radial dimensions, is now available. A bunch of new classes to manage hatches have been implemented. Specifically, AdgHatch is the entity to be added to the canvas, AdgFillStyle a generic abstract class that wraps cairo_pattern_t and AdgRuledFill the AdgFillStyle implementation to fill an hatch with a serie of parallel lines with custom spacing and angle. The AdgModel now supports named pairs, that is any coordinates -can be set explicitely (as before) or as a reference to a pair +can be set explicitly (as before) or as a reference to a pair set by the model. The AdgPoint helper class is the object used to manage this relationship. The API of dimension entities has been updated to accept AdgPoint instead of plain AdgPair. ADG 0.5.2 AdgADim, a new entity to quote angular dimensions, is implemented. The linear dimensions now accept the "outside" property to reflect the arrows around the extension lines. The default value of this property is ADG_THREE_STATE_UNKNOWN, meaning it will be computed at runtime, depending on quote size and available space. The new AdgEdges model can now be used to programmatically build a serie of vertical lines expressing the edges of another model. This can be used on symmetrical shapes, often met in turned parts. The entities now actively respond to extents requests: it is possible to know the necessary space before rendering them. Any matrix change now emits specific signals to fulfill this requirement. ADG 0.5.1 The new AdgMarker abstract class has been added to provide a common ancestor to marker entities. AdgArrow (a filled arrow) is its first renderable implementation. The rendering customization is now provided by AdgDress, an index that virtualizes the underlying AdgStyle and derived instances. This allows some advanced operation, such as overriding a style in a specific branch of the entity hierarchy. The deprecated AdgContext class has been removed. The AdgPath class has been splitted in AdgTrail and AdgPath. The former provides low level access to CpmlPath, allowing the implementation of paths that do not fit in the global/local model. AdgLDim internally uses a private AdgTrail to implement base and extension lines rendering. ADG 0.5.0 The old paper-model matrix approach has been superseded by a cleaner and smarter approach based on global/local maps. This resulted in a great code complexity reduction: check out the home page for further details. Parent-child relationship is no more tied to AdgContainer: dependencies between AdgEntities can now be expressed without a full-fledged container. AdgDim now uses internally four AdgToyText entities to render the quote. This has been made possible by the new parent-child APIs, simplyfing the rendering code a lot. A bunch of classes, most notably AdgRotable, AdgTranslatable and AdgPoint, have been superseded by the new global/local map approach and have been removed from the project. ADG 0.4.3 Higher level operators, such as chamfer and fillet, can now be used in the path definition. They do not work yet for Bézier curves but the infrastructure is in place. A Gtk+ widget for easily showing the canvas has been implemented. It is based on GtkDrawingArea and provides some interaction through the mouse, such as zooming and panning. It is not viable for serious work but it is funny to use and opened to improvements. The CPML library has been enriched with the missing functions needed by the above new features. Now it has a ..._length() and ..._near_pos() families of functions. The mathematic behind this library is far from complete: the overall design is quite stable but there are a lot of placeholders, especially when curves were involved. ADG 0.4.2 The work on model-view separation has been started: the AdgModel abstract class is now in place. Above this class, the new AdgPath model is derived, providing a full set of APIs to construct paths from scratch. Also, AdgPath gives full access the model through CPML, allowing to modify and manipulate the path data. The CPML library now natively supports arcs. Although cairo does not recognize arcs, a lot of work has been done to hide the complexity needed to provide an API that does the necessary trasformations transparently. The old AdgPath entity has been rewrote to AdgStroke. This is a stroked view of an AdgPath model. Its implementation is trivial as all the path complexity has moved to AdgPath. ADG 0.4.1 The CPML library has been boosted with additional APIs to browse segments/primitives and to compute intersections between different constructs. Now it has its own demo program (cpml-demo), showing some of the implemented feature. Although some function is still a stub, the overall infrastructure is likely to be definitive. All the CPML APIs are now properly documented. The ldim demo has been merged into the more complex adg-demo. Now the demo programs use the GtkBuilder feature, so the Gtk+ dependency has been raised to 2.12. ADG 0.4.0 The useless wrappers in AdgPath have been removed: now the standard cairo path API could be used in the path constructor callback. AdgPoint has been added. This new core struct can be used to represent a coordinate system with different model and space component. The new AdgToyText entity can be used to show arbitrary text using the cairo "toy" text API. Two interfaces, AdgTranslatable and AdgRotable, have been implemented to allow arbitrary positioning (using the new AdgPoint struct) and rotation of entities where this is applicable (e.g. AdgToyText). The CPML API has been almost rewrote from scratch: removed a lot of duplicated stuff, dropped CpmlPath (by using the original cairo_path_t) and implemented new functions, such as segment browsing (always in forward direction), reversing and transformation. README, NEWS, TODO and ChangeLog are now automatically generated, so redundancy is removed and the maintainability improved. The originals are kept in docbook format, allowing to use them also in html version. The adg-demo program now allows to save the sample drawing in png, pdf and postscript. Two useless toy text entities has been added. ADG 0.3.2 The dependency on libgcontainer has been dropped: now the adg canvas requires only cairo and Gtk+2 (will be optional). This allows to try the demo program (demo/adg-demo) on any decent GNU/Linux distribution without installing anything. The container logic has been included in AdgEntity (the GChildable portion) and AdgContainer (the GContainerable part). ADG 0.3.1 Implemented the "invalidate" signal to force the cache recomputation on a new rendering operation. The new cairo "toy" API has been used to do text management in AdgDim. This allows caching of the intermediate glyphs for improved performances (no profiling yet done). The cairo dependency has been raised to version 0.7.4 or more. The AdgLDim entity has been rewritten to full implement the new cache design. Other entities have been improved to allow a paper matrix other than the default identity matrix. ADG 0.3.0 Reworked styles and implemented AdgContext, a clean and well defined approach to manage rendering customization. Moved all the geometrical stuff in Cairo Path Manipulation Library (CPML), an internal library depending only on cairo. Greatly improved documentation. ADG 0.2.2 Strongly separated model and paper matrix concepts and updated their management using nested transformations. Reworked the rendering process in a cleaner way by propagating the "render" signal and using internal entity states to let the application know what changed from the previous rendering. ADG 0.2.1 Reworked the sources tree in a cleaner way (to help future development) and used private structs instead of populating the instance structure. ADG 0.2.0 This is the first public release of the ADG library. It is in a early stage of developement, but the goal is quite ambitious! diff --git a/po/it.po b/po/it.po index 95054091..87bc111d 100644 --- a/po/it.po +++ b/po/it.po @@ -1,1809 +1,1810 @@ # Italian translation for adg. # Copyright (C) 2010-2015 Nicola Fontana # This file is distributed under the same license as adg package. # # NOTE PER IL TRADUTTORE # # Per convenzione sono state adottate le seguenti traduzioni: # # dress -> vestito # mix -> combinazione # fallback -> implicito # map -> trasformazione # marker -> contrassegno # # Le seguenti parole sono state lasciate in inglese: # # canvas, named pair, path, top-level # # In ogni caso le parole 'fra apici' non devono essere tradotte. # msgid "" msgstr "" "Project-Id-Version: adg 0.9.3\n" "Report-Msgid-Bugs-To: http://dev.entidi.com/p/adg/issues/\n" -"POT-Creation-Date: 2022-02-13 11:21+0100\n" +"POT-Creation-Date: 2022-05-19 08:41+0200\n" "PO-Revision-Date: 2022-02-13 14:12+0100\n" "Last-Translator: Nicola Fontana \n" "Language-Team: \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: demo/adg-demo.c:96 demo/cpml-demo.c:289 msgid "Display version information" msgstr "Stampa le informazioni sulla versione" #: demo/adg-demo.c:98 msgid "Show the boundary boxes of every entity" msgstr "Mostrare il riquadro contenitore di ogni entità" #: demo/adg-demo.c:104 msgid "- ADG demonstration program" msgstr "- Programma dimostrativo della ADG" #: demo/adg-demo.c:128 msgid "Error from adg-demo" msgstr "Errore da adg-demo" #: demo/adg-demo.c:801 msgid "Requested format not supported" msgstr "Formato richiesto non supportato" #: demo/adg-demo.c:1168 msgid "adg-demo.ui not found!\n" msgstr "adg-demo.ui non trovato!\n" #: demo/adg-demo.ui.in:8 msgid "adg-demo - Demostration program" msgstr "adg-demo - Programma dimostrativo" #: demo/adg-demo.ui.in:133 msgid "About adg-demo" msgstr "Informazioni su adg-demo" #: demo/adg-demo.ui.in:145 msgid "Copyright (C) 2007-2022 Nicola Fontana" msgstr "Copyright (C) 2007-2022 Nicola Fontana" #: demo/adg-demo.ui.in:146 msgid "Demonstration program for the ADG library" msgstr "Programma dimostrativo della libreria ADG" #: demo/adg-demo.ui.in:148 msgid "" "The ADG library is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU Lesser General Public\n" "License as published by the Free Software Foundation; either\n" "version 2 of the License, or (at your option) any later version.\n" "\n" "This library is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty\n" "of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "See the GNU Lesser General Public License for more details.\n" "\n" "You should have received a copy of the GNU Lesser General Public\n" "License along with this library; if not, write to the\n" "Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n" "Boston, MA 02110-1301, USA." msgstr "" "La libreria ADG è software libero; puoi ridistribuirla e/o\n" "modificarla secondo i termini della GNU Lesser General Public\n" "License così come pubblicata dalla Free Software Foundation;\n" "sia la versione 2 della licenza o (a tua scelta) qualsiasi\n" "versione successiva.\n" "\n" "Questa libreria è distribuita nella speranza che sia utile,\n" "ma SENZA ALCUNA GARANZIA; nemmeno con la garanzia di VIABILITÀ\n" "SUL MERCATO o di APPROPRIATEZZA PER UN PARTICOLARE FINE. Per\n" "maggiori dettagli, vedere il testo originale in inglese.\n" "\n" "Dovresti aver ricevuto una copia della GNU Lesser General Public\n" "License insieme a questa libreria; in caso contrario, scrivere a\n" "Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n" "Boston, MA 02110-1301, USA." #: demo/adg-demo.ui.in:181 msgid "Customize drawing" msgstr "Impostazioni disegno" #: demo/adg-demo.ui.in:233 msgid "_Diameter rail:" msgstr "_Diametro guida:" #: demo/adg-demo.ui.in:260 msgid "_Total length:" msgstr "Lunghezza _totale:" #: demo/adg-demo.ui.in:275 msgid "_B quote:" msgstr "Quota _B:" #: demo/adg-demo.ui.in:307 msgid "Diameter _2:" msgstr "Diametro _2:" #: demo/adg-demo.ui.in:337 msgid "Length 2:" msgstr "Lunghezza 2:" #: demo/adg-demo.ui.in:369 msgid "Diameter _3:" msgstr "Diametro _3:" #: demo/adg-demo.ui.in:384 msgid "Diameter _4:" msgstr "Diametro _4:" #: demo/adg-demo.ui.in:429 msgid "Length 3:" msgstr "Lunghezza 3:" #: demo/adg-demo.ui.in:477 msgid "Body data" msgstr "Dati corpo" #: demo/adg-demo.ui.in:506 msgid "_Head diameter:" msgstr "Diametro _testa:" #: demo/adg-demo.ui.in:532 msgid "Head tickness:" msgstr "Spessore testa:" #: demo/adg-demo.ui.in:560 msgid "Tablet diameter:" msgstr "Diametro pastiglia:" #: demo/adg-demo.ui.in:589 msgid "Cham_fered head" msgstr "Testa sm_ussata" #: demo/adg-demo.ui.in:604 msgid "_Rounded head" msgstr "Testa _raggiata" #: demo/adg-demo.ui.in:625 msgid "Head radius:" msgstr "Raggio testa:" #: demo/adg-demo.ui.in:664 msgid "Head data" msgstr "Dati testa" #: demo/adg-demo.ui.in:693 msgid "Hole diameter:" msgstr "Diametro foro:" #: demo/adg-demo.ui.in:719 msgid "Hole depth:" msgstr "Profondità foro:" #: demo/adg-demo.ui.in:751 msgid "Rear hole data" msgstr "Dati foratura" #: demo/adg-demo.ui.in:777 msgid "Groove machining" msgstr "Lavorazione scarico" #: demo/adg-demo.ui.in:795 msgid "Groove position:" msgstr "Posizione scarico:" #: demo/adg-demo.ui.in:829 msgid "Groove diameter:" msgstr "Diametro scarico:" #: demo/adg-demo.ui.in:861 msgid "Groove thickness:" msgstr "Spessore scarico:" #: demo/adg-demo.ui.in:902 msgid "Additional machinings" msgstr "Lavorazioni aggiuntive" #: demo/adg-demo.ui.in:918 msgid "Dimensions" msgstr "Dimensioni" #: demo/adg-demo.ui.in:943 msgid "_Title:" msgstr "_Titolo:" #: demo/adg-demo.ui.in:956 msgid "SAMPLE DRAWING" msgstr "DISEGNO CAMPIONE" #: demo/adg-demo.ui.in:967 msgid "_Drawing:" msgstr "D_isegno:" #: demo/adg-demo.ui.in:982 msgid "EXAMPLE" msgstr "ESEMPIO" #: demo/adg-demo.ui.in:995 msgid "A_uthor:" msgstr "_Autore:" #: demo/adg-demo.ui.in:1023 msgid "D_ate:" msgstr "_Data:" #: demo/adg-demo.ui.in:1056 msgid "Metadata" msgstr "Metadati" #: demo/adg-demo.ui.in:1066 msgid "Title block" msgstr "Riquadro delle intestazioni" #: demo/adg-demo.ui.in:1139 msgid "Help on adg-demo" msgstr "Aiuto di adg-demo" #: demo/adg-demo.ui.in:1145 msgid "How to use adg-demo" msgstr "Come usare adg-demo" #: demo/adg-demo.ui.in:1146 msgid "" "This demo is just a sample of what can be accomplished by using the ADG " "library. It is by no means intended to be the only kind of application " "possible.\n" "\n" "To better see the differences between local and global space, some mouse " "interaction is needed: rotate the mouse wheel to zoom in and out in " "local space, drag it with the wheel pressed to pan and keep the " "shift pressed while zooming and panning to do the same in global " "space.\n" "\n" "A left click on the drawing area will restore the paper zoom and " "position, a right click will autoscale and center the drawing." msgstr "" "Questo dimostrativo è solo un esempio di ciò che può essere sviluppato " "usando la libreria ADG. Non è assolutamente da considerare il solo tipo di " "applicazione possibile.\n" "\n" "Per vedere meglio le differenze tra spazio globale e locale, è necessario " "interagire con il mouse: ruotare la rotellina per ingrandire o " "rimpicciolire nello spazio locale, trascinare con la rotellina " "premuta per traslare e tenere il tasto maiuscolo premuto durante " "l'ingrandimento, il rimpicciolimento o la traslazione per eseguire le " "medesime operazioni nello spazio globale.\n" "\n" "Un click sinistro nell'area di disegno ripristinerà ingrandimento e " "posizione del foglio, un click destro autoscalerà e centrerà il " "disegno." #: demo/adg-demo.ui.in:1194 msgid "Save current drawing" msgstr "Salva disegno corrente" #: demo/adg-demo.ui.in:1220 msgid "Guessed from e_xtension" msgstr "Rileva dall'e_stensione" #: demo/adg-demo.ui.in:1235 msgid "PNG _image" msgstr "_Immagine PNG" #: demo/adg-demo.ui.in:1251 msgid "S_VG image" msgstr "Immagine S_VG" #: demo/adg-demo.ui.in:1267 msgid "P_DF document" msgstr "Documento P_DF" #: demo/adg-demo.ui.in:1283 msgid "_PostScript file" msgstr "File _PostScript" #: demo/adg-demo.ui.in:1302 msgid "File format" msgstr "Formato file" #: demo/cpml-demo.c:192 msgid "cpml-demo.ui not found!\n" msgstr "cpml-demo.ui non trovato!\n" #: demo/cpml-demo.c:293 msgid "- CPML demonstration program" msgstr "- Programma dimostrativo della CPML" #: demo/cpml-demo.c:476 #, c-format msgid "Unable to get arc info (%lf, %lf) (%lf, %lf) (%lf, %lf)\n" msgstr "Impossibile elaborare l'arco (%lf, %lf) (%lf, %lf) (%lf, %lf)\n" #: demo/cpml-demo.ui:24 msgid "" "Browsing\n" "Primitives and segments" msgstr "" "Navigazione\n" "Primitive e segmenti" #: demo/cpml-demo.ui:29 msgid "" "Arcs\n" "Native support for arcs and circles" msgstr "" "Archi\n" "Supporto nativo per archi e cerchi" #: demo/cpml-demo.ui:34 msgid "" "Intersections\n" "Geometrical helper functions" msgstr "" "Intersezioni\n" "Funzioni geometriche di supporto" #: demo/cpml-demo.ui:39 msgid "" "Offset curves\n" "Test bed for cubic Bézier offset" msgstr "" "Offset curve\n" "Banco prova per l'offset delle Bézier cubiche" #: demo/cpml-demo.ui:44 msgid "" "Offset segments\n" "Offseting complex segments" msgstr "" "Offset segmenti\n" "Offset di segmenti complessi" #: demo/cpml-demo.ui:74 msgid "Pages" msgstr "Pagine" #: demo/cpml-demo.ui:176 msgid "Browsing subject:" msgstr "Soggetto navigazione:" #: demo/cpml-demo.ui:247 msgid "Browsing" msgstr "Navigazione" #: demo/cpml-demo.ui:267 msgid "Arcs" msgstr "Archi" #: demo/cpml-demo.ui:288 msgid "Intersections" msgstr "Intersezioni" #: demo/cpml-demo.ui:398 msgid "Offset algorithm:" msgstr "Algoritmo di offset:" #: demo/cpml-demo.ui:419 msgid "Offset curves" msgstr "Curve parallele" #: demo/cpml-demo.ui:440 msgid "Offset segments" msgstr "Segmenti paralleli" #: demo/demo.c:72 msgid "Invalid arguments: arg[0] not set" msgstr "Argomenti non validi: arg[0] è vuoto" #: src/adg/adg-adim.c:129 msgid "First Origin" msgstr "Prima Origine" #: src/adg/adg-adim.c:130 msgid "" "Where the first line comes from: this point is used toghether with \"ref1\" " "to align the first extension line" msgstr "" "Da dove arriva la prima linea: questo punto è usato insieme a \"ref1\" per " "allineare la prima linea di estensione" #: src/adg/adg-adim.c:136 msgid "Second Origin" msgstr "Seconda Origine" #: src/adg/adg-adim.c:137 msgid "" "Where the second line comes from: this point is used toghether with \"ref2\" " "to align the second extension line" msgstr "" "Da dove arriva la seconda linea: questo punto è usato insieme a \"ref2\" per " "allineare la seconda linea di estensione" #: src/adg/adg-adim.c:143 src/adg/adg-ldim.c:143 msgid "Has First Extension Line flag" msgstr "Flag di Prima Linea di Estensione Presente" #: src/adg/adg-adim.c:144 src/adg/adg-ldim.c:144 msgid "Show (TRUE) or hide (FALSE) the first extension line" msgstr "Mostrare (TRUE) o nascondere (FALSE) la prima linea di estensione" #: src/adg/adg-adim.c:149 src/adg/adg-ldim.c:149 msgid "Has Second Extension Line flag" msgstr "Flag di Seconda Linea di Estensione Presente" #: src/adg/adg-adim.c:150 src/adg/adg-ldim.c:150 msgid "Show (TRUE) or hide (FALSE) the second extension line" msgstr "Mostrare (TRUE) o nascondere (FALSE) la seconda linea di estensione" #: src/adg/adg-adim.c:1188 msgid "Trying to set an angular dimension on parallel lines" msgstr "Si è provato a quotare con un angolo due linee parallele" #: src/adg/adg-alignment.c:103 src/adg/adg-canvas.c:215 #: src/adg/adg-gtk-area.c:587 msgid "Factor" msgstr "Fattore" #: src/adg/adg-alignment.c:104 msgid "" "Portion of extents, either in x and y, the content will be displaced: a " "(0.5, 0.5) factor means the origin is the middle point of the extents" msgstr "" "Frazione dell'estensione, sia in x che in y, ove il contenuto sarà " "posizionato: per esempio, usando (0.5, 0.5) l'origine si troverà al centro " "dell'esetensione" #: src/adg/adg-arrow.c:99 msgid "Arrow Angle" msgstr "Angolo Freccia" #: src/adg/adg-arrow.c:100 msgid "The opening angle of the arrow" msgstr "L'angolo di apertura della freccia" #: src/adg/adg-canvas.c:208 msgid "Canvas Size" msgstr "Dimensione Canvas" #: src/adg/adg-canvas.c:209 msgid "" "The size set on this canvas: use 0 to have an automatic dimension based on " "the canvas extents" msgstr "" "La dimensione impostata per questo canvas: usare 0 per abilitare il " "dimensionamento automatico basato sui limiti del canvas" #: src/adg/adg-canvas.c:216 msgid "Global space units per point (1 point == 1/72 inch)" msgstr "Unità spazio globale per punto (1 punto == 1/72 di pollice)" #: src/adg/adg-canvas.c:222 msgid "Valid Scales" msgstr "Scale Valide" #: src/adg/adg-canvas.c:223 msgid "List of scales to be tested while autoscaling the drawing" msgstr "Elenco di scale da provare durante l'autoscalatura del disegno" #: src/adg/adg-canvas.c:229 msgid "Background Dress" msgstr "Vestito Sfondo" #: src/adg/adg-canvas.c:230 msgid "The color dress to use for the canvas background" msgstr "Il vestito colore da usare come sfondo per il canvas" #: src/adg/adg-canvas.c:236 src/adg/adg-logo.c:107 #: src/adg/adg-table-style.c:107 msgid "Frame Dress" msgstr "Vestito Riquadro" #: src/adg/adg-canvas.c:237 msgid "Line dress to use while drawing the frame around the canvas" msgstr "Vesito linea da usare durante il rendering della cornice del canvas" #: src/adg/adg-canvas.c:243 msgid "Title Block" msgstr "Blocco delle Iscrizioni" #: src/adg/adg-canvas.c:244 msgid "The title block to assign to this canvas" msgstr "Il blocco delle iscrizioni da allegare a questo canvas" #: src/adg/adg-canvas.c:250 msgid "Top Margin" msgstr "Margine Superiore" #: src/adg/adg-canvas.c:251 msgid "The margin (in global space) to leave above the frame" msgstr "Lo spazio (in global space) da lasciare sopra la cornice" #: src/adg/adg-canvas.c:257 msgid "Right Margin" msgstr "Margine Destro" #: src/adg/adg-canvas.c:258 msgid "The margin (in global space) to leave empty at the right of the frame" msgstr "Lo spazio (in global space) da lasciare alla destra della cornice" #: src/adg/adg-canvas.c:264 msgid "Bottom Margin" msgstr "Margine Inferiore" #: src/adg/adg-canvas.c:265 msgid "The margin (in global space) to leave empty below the frame" msgstr "Lo spazio (in global space) da lasciare vuoto sotto la cornice" #: src/adg/adg-canvas.c:271 msgid "Left Margin" msgstr "Margine Sinistro" #: src/adg/adg-canvas.c:272 msgid "The margin (in global space) to leave empty at the left of the frame" msgstr "" "Lo spazio (nello spazio globale) da lasciare vuoto alla sinistra della " "cornice" #: src/adg/adg-canvas.c:278 src/adg/adg-table.c:151 msgid "Has Frame Flag" msgstr "Flag di Riquadro Presente" #: src/adg/adg-canvas.c:279 msgid "" "If enabled, a frame using the frame dress will be drawn around the canvas " "extents, taking into account the margins" msgstr "" "Se abilitato verrà disegnata una cornice, usando vestito cornice, attorno ai " "limiti dal canvas tenendo in considerazione i margini" #: src/adg/adg-canvas.c:285 msgid "Top Padding" msgstr "Spaziatura Superiore" #: src/adg/adg-canvas.c:286 msgid "" "The padding (in global space) to leave empty above between the drawing and " "the frame" msgstr "" "La spaziatura (in global space) da lasciare vuota sopra tra il disegno e la " "cornice" #: src/adg/adg-canvas.c:292 msgid "Right Padding" msgstr "Spaziatura Destra" #: src/adg/adg-canvas.c:293 msgid "" "The padding (in global space) to leave empty at the right between the " "drawing and the frame" msgstr "" "La spaziatura (in global space) da lasciare vuota a destra tra il disegno e " "la cornice" #: src/adg/adg-canvas.c:299 msgid "Bottom Padding" msgstr "Spaziatura Inferiore" #: src/adg/adg-canvas.c:300 msgid "" "The padding (in global space) to leave empty below between the drawing and " "the frame" msgstr "" "La spaziatura (in global space) da lasciare vuota a sotto tra il disegno e " "la cornice" #: src/adg/adg-canvas.c:306 msgid "Left Padding" msgstr "Spaziatura Sinistra" #: src/adg/adg-canvas.c:307 msgid "" "The padding (in global space) to leave empty at the left between the drawing " "and the frame" msgstr "" "La spaziatura (in global space) da lasciare vuota a sinistra tra il disegno " "e la cornice" #: src/adg/adg-color-style.c:91 msgid "Red Channel" msgstr "Canale Rosso" #: src/adg/adg-color-style.c:92 msgid "The red value, where 0 means no red and 1 is full red" msgstr "Quantità di rosso, dove 0 significa assenza di rosso e 1 rosso pieno" #: src/adg/adg-color-style.c:98 msgid "Green Channel" msgstr "Canale Verde" #: src/adg/adg-color-style.c:99 msgid "The green value, where 0 means no green and 1 is full green" msgstr "Quantità di verde, dove 0 significa assenza di verde e 1 verde pieno" #: src/adg/adg-color-style.c:105 msgid "Blue Channel" msgstr "Canale Blu" #: src/adg/adg-color-style.c:106 msgid "The blue value, where 0 means no blue and 1 is full blue" msgstr "Quantità di blu, dove 0 significa assenza di blu e 1 blu pieno" #: src/adg/adg-color-style.c:112 msgid "Alpha Channel" msgstr "Canale Alfa" #: src/adg/adg-color-style.c:113 msgid "" "The alpha value, where 0 means completely transparent and 1 is fully opaque" msgstr "" "Il valore alfa, dove 0 significa totalmente trasparente e 1 completamente " "opaco" #: src/adg/adg-container.c:135 msgid "Child" msgstr "Figlio" #: src/adg/adg-container.c:136 msgid "Can be used to add a new child to the container" msgstr "Può essere usato per aggiungere una nuova entità al contenitore" #: src/adg/adg-container.c:406 #, c-format msgid "%s: signal '%s' is invalid for instance %p" msgstr "%s: segnale '%s' non valido per l'oggetto %p" #: src/adg/adg-container.c:529 #, c-format msgid "" "Attempting to add an entity with type %s to a container of type %s, but the " "entity is already inside a container of type %s" msgstr "" "Si è provato ad aggiungere una entità di tipo %s ad un contenitore di tipo " "%s ma l'entità è già all'interno di un contenitore di tipo %s" #: src/adg/adg-container.c:560 #, c-format msgid "" "Attempting to remove an entity with type %s from a container of type %s, but " "the entity is not present" msgstr "" "Si è provato ad eliminare una entità di tipo %s da un contenitore di tipo %s " "ma l'entità non è presente" #: src/adg/adg-dim.c:166 msgid "Dimension Dress" msgstr "Vestito Dimensione" #: src/adg/adg-dim.c:167 msgid "The dress to use for rendering this dimension" msgstr "Il vestito da usare per il rendering di questa dimensione" #: src/adg/adg-dim.c:173 msgid "First Reference" msgstr "Primo Riferimento" #: src/adg/adg-dim.c:174 msgid "First reference point of the dimension" msgstr "Primo punto di riferimento della dimensione" #: src/adg/adg-dim.c:180 msgid "Second Reference" msgstr "Secondo Riferimento" #: src/adg/adg-dim.c:181 msgid "Second reference point of the dimension" msgstr "Secondo punto di riferimento della dimensione" #: src/adg/adg-dim.c:187 src/adg/adg-marker.c:148 msgid "Position" msgstr "Posizione" #: src/adg/adg-dim.c:188 msgid "" "The reference position of the quote: it will be combined with \"level\" to " "get the real quote position" msgstr "" "Il punto di riferimento per il posizionamento della quota: sarà combinato " "con il \"level\" per ottenere la posizione reale della quota" #: src/adg/adg-dim.c:194 msgid "Level" msgstr "Livello" #: src/adg/adg-dim.c:195 msgid "" "The dimension level, that is the factor to multiply the baseline spacing " "(defined in the dimension style) to get the offset from pos where the quote " "should be rendered" msgstr "" "Il livello della dimensione, ossia il fattore per moltiplicare la spaziatura " "tra le linee base (definito nello stile della dimensione) per ottenere " "l'offset dal punto di posizionamento dove la quota dovrebbe essere riportata" #: src/adg/adg-dim.c:201 msgid "Outside" msgstr "Esterna" #: src/adg/adg-dim.c:202 msgid "" "Whether the arrows must be inside the extension lines (ADG_THREE_STATE_OFF), " "must be extended outside the extension lines (ADG_THREE_STATE_ON) or should " "be automatically handled depending on the available space" msgstr "" "Specifica se le freccie devono stare all'interno delle linee di estensione " "(ADG_THREE_STATE_OFF), devono estendersi all'esterno delle linee di " "estensione (ADG_THREE_STATE_ON) oppure questa decisione viene gestita " "automaticamente in base allo spazio disponibile" #: src/adg/adg-dim.c:208 msgid "Detached Quote" msgstr "Quota Distaccata" #: src/adg/adg-dim.c:209 msgid "" "Where the quote must be positioned: in the middle of the base line " "(ADG_THREE_STATE_OFF), near the pos point (ADG_THREE_STATE_ON) or should be " "automatically deducted depending on the available space" msgstr "" "Dove deve essere posizionata la quota: nel mezzo della linea di base " "(ADG_THREE_STATE_OFF), vicino al punto di posizionamento " "(ADG_THREE_STATE_ON) oppure se questa decisione è subordinata allo spazio " "disponibile" #: src/adg/adg-dim.c:215 msgid "Value Template" msgstr "Template Valore" #: src/adg/adg-dim.c:216 msgid "" "The template string to be used for generating the set value of the quote" msgstr "" "La stringa di template da usare per generare il valore nominale della quota" #: src/adg/adg-dim.c:222 msgid "Minimum Value or Low Tolerance" msgstr "Valore Minimo o Tolleranza Inferiore" #: src/adg/adg-dim.c:223 msgid "" "The minimum value allowed or the lowest tolerance from value (depending of " "the dimension style): set to NULL to suppress" msgstr "" "Il valore minimo ammesso o la tolleranza inferiore dal valore nominale (a " "seconda dello stile dimensione): impostare a NULL per disattivarlo" #: src/adg/adg-dim.c:229 msgid "Maximum Value or High Tolerance" msgstr "Valore Massimo o Tolleranza Superiore" #: src/adg/adg-dim.c:230 msgid "" "The maximum value allowed or the highest tolerance from value (depending of " "the dimension style): set to NULL to suppress" msgstr "" "Il valore massimo ammesso o la tolleranza superiore dal valore nominale (a " "seconda dello stile dimensione): impostare a NULL per disattivarlo" #: src/adg/adg-dim.c:1350 #, c-format msgid "'%s' is missing" msgstr "'%s' assente" #: src/adg/adg-dim.c:1383 #, c-format msgid "'%s' and '%s' cannot be coincident (%lf, %lf)" msgstr "'%s' e '%s' non possono essere coincidenti (%lf, %lf)" #: src/adg/adg-dim.c:1613 #, c-format msgid "AdgDim::compute_geometry not implemented for '%s'" msgstr "AdgDim::compute_geometry non implementata per '%s'" #: src/adg/adg-dim.c:1621 #, c-format msgid "AdgDim::default_value not implemented for '%s'" msgstr "AdgDim::default_value non implementato da '%s'" #: src/adg/adg-dim-style.c:152 msgid "First Marker" msgstr "Primo Contrassegno" #: src/adg/adg-dim-style.c:153 msgid "The template entity to use as first marker" msgstr "L'entità guida da usare per il primo contrassegno" #: src/adg/adg-dim-style.c:159 msgid "Second Marker" msgstr "Secondo Contrassegno" #: src/adg/adg-dim-style.c:160 msgid "The template entity to use as second marker" msgstr "L'entità guida da usare per il secondo contrassegno" #: src/adg/adg-dim-style.c:166 src/adg/adg-font-style.c:100 #: src/adg/adg-line-style.c:96 src/adg/adg-table-style.c:93 msgid "Color Dress" msgstr "Vestito Colore" #: src/adg/adg-dim-style.c:167 msgid "Color dress for the whole dimension" msgstr "Vestito colore da usare per l'intera dimensione" #: src/adg/adg-dim-style.c:173 src/adg/adg-table-style.c:121 msgid "Value Dress" msgstr "Vestito Valore" #: src/adg/adg-dim-style.c:174 msgid "Font dress for the nominal value of the dimension" msgstr "Vestito del font per il valore nominale della dimensione" #: src/adg/adg-dim-style.c:180 msgid "Minimum Limit Dress" msgstr "Vestito Limite Minimo" #: src/adg/adg-dim-style.c:181 msgid "Font dress for the lower limit value" msgstr "Vestito del font per il valore limite basso" #: src/adg/adg-dim-style.c:187 msgid "Maximum Limit Dress" msgstr "Vestito Limite Massimo" #: src/adg/adg-dim-style.c:188 msgid "Font dress for the upper limit value" msgstr "Vestito del font per il valore limite alto" #: src/adg/adg-dim-style.c:194 src/adg/adg-ruled-fill.c:109 #: src/adg/adg-stroke.c:102 msgid "Line Dress" msgstr "Vestito Linea" #: src/adg/adg-dim-style.c:195 msgid "Line dress for the baseline and the extension lines" msgstr "Vestito della linea di quotatura e delle linee di estensione" #: src/adg/adg-dim-style.c:201 msgid "From Offset" msgstr "Distanza Da" #: src/adg/adg-dim-style.c:202 msgid "" "Offset (in global space) of the extension lines from the path to the quote" msgstr "" "Distanza (nello spazio globale) tra la linea di estensione e la forma da " "quotare" #: src/adg/adg-dim-style.c:208 msgid "To Offset" msgstr "Distanza A" #: src/adg/adg-dim-style.c:209 msgid "" "How many extend (in global space) the extension lines after hitting the " "baseline" msgstr "" "Quanto allungare (nello spazio globale) le linee di estensione oltre la " "linea di quotatura" #: src/adg/adg-dim-style.c:215 msgid "Beyond Length" msgstr "Lunghezza Oltre" #: src/adg/adg-dim-style.c:216 msgid "" "How much the baseline should be extended (in global space) beyond the " "extension lines on dimensions with outside markers" msgstr "" "Quanto la linea di quotatura si può allungare (nello spazio globale) oltre " "le linee di estensione nelle dimensioni con contrassegni esterni" #: src/adg/adg-dim-style.c:222 msgid "Baseline Spacing" msgstr "Distanza Linee di Quotatura" #: src/adg/adg-dim-style.c:223 msgid "Distance between two consecutive baselines while stacking dimensions" msgstr "Distanza tra due linee di quotatura nelle dimensioni sovrapposte" #: src/adg/adg-dim-style.c:229 msgid "Limits Spacing" msgstr "Spaziatura Limiti" #: src/adg/adg-dim-style.c:230 msgid "Distance between limits/tolerances" msgstr "Distanza tra i limiti/tolleranze" #: src/adg/adg-dim-style.c:236 msgid "Quote Shift" msgstr "Spiazzamento Quota" #: src/adg/adg-dim-style.c:237 msgid "" "Used to specify a smooth displacement (in global space) of the quote by " "taking as reference the perfect compact position (the middle of the baseline " "on common linear dimension, for instance)" msgstr "" "Usato per specificare un piccolo scostamento (in global space) della quota " "con riferimento alla posizione compatta ideale (nelle comuni dimensioni " "lineari, la metà della linea di base, per esempio)" #: src/adg/adg-dim-style.c:243 msgid "Limits Shift" msgstr "Scostamento dei Limiti" #: src/adg/adg-dim-style.c:244 msgid "" "Used to specify a smooth displacement (in global space) for the limits/" "tolerances by taking as reference the perfect compact position" msgstr "" "Usato per specificare un piccolo scostamento (in global space) per le " "tolleranze/limiti prendendo come riferimento la posizione compatta ideale" #: src/adg/adg-dim-style.c:250 msgid "Number Format" msgstr "Formato Numero" #: src/adg/adg-dim-style.c:251 msgid "" "The format (in printf style) of the numeric component of the basic value" msgstr "" "Il formato (in modalità printf) del componente numerico della quota nominale" #: src/adg/adg-dim-style.c:257 msgid "Number Arguments" msgstr "Argomenti Numero" #: src/adg/adg-dim-style.c:258 msgid "" "The arguments to pass to the format function: see " "adg_dim_style_set_number_arguments() for further details" msgstr "" "Gli argomenti da passare alla funzione di formattazione: vedere " "adg_dim_style_set_number_arguments() per maggiori dettagli" #: src/adg/adg-dim-style.c:264 msgid "Number Tag" msgstr "Tag di Quota" #: src/adg/adg-dim-style.c:265 msgid "The tag to substitute inside the value template string" msgstr "Il tag da sostituire all'interno della stringa template del valore" #: src/adg/adg-dim-style.c:271 msgid "Rounded Value Decimals" msgstr "Decimali Valore Arrotondato" #: src/adg/adg-dim-style.c:272 msgid "" "Number of significant decimals to use in the format string for rounded " "values (-1 to disable)" msgstr "" "Numero di decimali significativi da usare come formato formato per i valori " "arrotondati (-1 per disabilitare)" #: src/adg/adg-dim-style.c:279 msgid "Raw Value Decimals" msgstr "Decimali Valore Originale" #: src/adg/adg-dim-style.c:280 msgid "" "Number of significant decimals the raw value must be rounded to (-1 to " "disable)" msgstr "" "Numero di decimali significativi a cui il valore originale deve essere " "arrotondato (-1 per disabilitare)" #: src/adg/adg-dress.c:338 #, c-format msgid "" "%s: the fallback style of '%s' dress (%d) must be a '%s' derived type, but a " "'%s' has been provided" msgstr "" "%s: lo stile implicito del vestito '%s' (%d) deve essere un tipo derivato da " "'%s' mentre è stato fornito un '%s'" #: src/adg/adg-edges.c:120 msgid "Source" msgstr "Originale" #: src/adg/adg-edges.c:121 msgid "The source from which the edges should be computed from" msgstr "L'originale da dove ottenere le linee spigolo" #: src/adg/adg-edges.c:127 msgid "Axis Angle" msgstr "Angolo Asse" #: src/adg/adg-edges.c:128 msgid "" "The angle of the axis of the source trail: it is implied this axis passes " "through (0,0)" msgstr "" "L'angolo dellasse dell'originale: è sottinteso che questo asse passerà per " "(0;0)" #: src/adg/adg-edges.c:134 msgid "Critical Angle" msgstr "Angolo Critico" #: src/adg/adg-edges.c:135 msgid "" "The angle that defines which corner generates an edge (if the corner angle " "is greater than this critical angle) and which edge is ignored" msgstr "" "L'angolo che definisce quando considerare uno spigolo (se l'angolo dello " "spigolo è maggiore di questo valore) e quando ignorarlo" #: src/adg/adg-entity.c:171 msgid "Floating Entity" msgstr "Entità Flottante" #: src/adg/adg-entity.c:172 msgid "" "Flag that includes (FALSE) or excludes (TRUE) this entity from the " "computation of the parent entity extents" msgstr "" "Impostazione per includere (FALSE) o escludere (TRUE) questa entità dal " "calcolo dell'estensione del genitore" #: src/adg/adg-entity.c:177 msgid "Parent Entity" msgstr "Entità Genitore" #: src/adg/adg-entity.c:178 msgid "The parent entity of this entity or NULL if this is a top-level entity" msgstr "" "L'entità che contiene questa entità o NULL se l'entità corrente è una top-" "level" #: src/adg/adg-entity.c:184 msgid "Global Map" msgstr "Trasformazione Globale" #: src/adg/adg-entity.c:185 msgid "" "The transformation to be combined with the parent ones to get the global " "matrix" msgstr "" "La trasformazione da combinare insieme a quelle dei progenitori per ottenere " "la matrice globale" #: src/adg/adg-entity.c:191 msgid "Local Map" msgstr "Trasformazione Locale" #: src/adg/adg-entity.c:192 msgid "" "The local transformation that could be used to compute the local matrix in " "the way specified by the #AdgEntity:local-mix property" msgstr "" "La trasformazione locale che può venir usata per calcolare la matrice locale " "nella modalità specificata dalla proprietà #AdgEntity:local-method" #: src/adg/adg-entity.c:198 msgid "Local Mix Method" msgstr "Metodo di Combinazione Locale" #: src/adg/adg-entity.c:199 msgid "" "Define how the local maps of the entity and its ancestors should be combined " "to get the local matrix" msgstr "" "Specifica come le trasformazioni locali dell'entità corrente e dei suoi " "progenitori devono essere combinate per ottenere la matrice locale" #: src/adg/adg-entity.c:967 #, c-format msgid "%s: '%s' is not compatible with '%s' for '%s' dress (%d)" msgstr "%s: '%s' non è compatibile con '%s' per il vestito '%s' (%d)" #: src/adg/adg-entity.c:1317 #, c-format msgid "%s: requested to mix the maps using an undefined mix method" msgstr "%s: metodo di combinazione delle trasformazioni sconosciuto" #: src/adg/adg-entity.c:1360 #, c-format msgid "%s: 'arrange' method not implemented for type '%s'" msgstr "%s: metodo 'arrange' non implementato per il tipo '%s'" #: src/adg/adg-entity.c:1376 #, c-format msgid "%s: 'render' method not implemented for type '%s'" msgstr "%s: metodo 'render' non implementato per il tipo '%s'" #: src/adg/adg-fill-style.c:106 msgid "Pattern" msgstr "Pattern" #: src/adg/adg-fill-style.c:107 msgid "The cairo pattern set for this entity" msgstr "Il pattern cairo impostato per questa entità" #: src/adg/adg-fill-style.c:279 #, c-format msgid "%s: pattern undefined for type '%s'" msgstr "%s: è stato richiesto un segmento indefinito per il tipo '%s'" #: src/adg/adg-font-style.c:101 msgid "The fallback color dress to bind to this style" msgstr "Il vestito colore implicito da associare a questo stile" #: src/adg/adg-font-style.c:107 msgid "Font Family" msgstr "Famiglia di Font" #: src/adg/adg-font-style.c:108 msgid "The font family name, encoded in UTF-8" msgstr "Il nome della famiglia di font, codificata in UTF-8" #: src/adg/adg-font-style.c:114 msgid "Font Slant" msgstr "Slant del Font" #: src/adg/adg-font-style.c:115 msgid "Variant of a font face based on its slant" msgstr "Variante del font basato sullo slant" #: src/adg/adg-font-style.c:121 msgid "Font Weight" msgstr "Spessore del Font" #: src/adg/adg-font-style.c:122 msgid "Variant of a font face based on its weight" msgstr "Variante del font basato sul suo spessore" #: src/adg/adg-font-style.c:128 msgid "Font Size" msgstr "Dimensione Font" #: src/adg/adg-font-style.c:129 msgid "Font size in user space units" msgstr "Dimensione del font nello spazio utente" #: src/adg/adg-font-style.c:135 msgid "Font Antialiasing Mode" msgstr "Modalità di Antialiasing del Font" #: src/adg/adg-font-style.c:136 msgid "Type of antialiasing to do when rendering text" msgstr "Tipo di antialiasing da applicare durante il disegno del testo" #: src/adg/adg-font-style.c:142 msgid "Font Subpixel Order" msgstr "Ordine di Subpixel del Font" # TODO: questa traduzione fa cagare #: src/adg/adg-font-style.c:143 msgid "" "The order of color elements within each pixel on the display device when " "rendering with subpixel antialiasing mode" msgstr "" "Ordine di colori all'interno di ogni pixel del display durante il disegno " "del testo con modalità di antialiasing subpixel abilitata" #: src/adg/adg-font-style.c:150 msgid "Type of Hinting" msgstr "Tipo di Hinting" #: src/adg/adg-font-style.c:151 msgid "" "How outlines must fit to the pixel grid in order to improve the glyph " "appearance" msgstr "" "Come il bordo deve adattarsi alla griglia di pixel per migliorare " "l'apparenza del segno" #: src/adg/adg-font-style.c:157 msgid "Font Metric Hinting" msgstr "Metrica di Hinting del Font" #: src/adg/adg-font-style.c:158 msgid "" "Whether to hint font metrics, that is align them to integer values in device " "space" msgstr "" "Specifica se adottare l'hint metrico, ossia se applicare l'allineamento a " "valori interi nello spazio dispositivo" #: src/adg/adg-gtk-area.c:580 msgid "Canvas" msgstr "Canvas" #: src/adg/adg-gtk-area.c:581 msgid "The canvas to be shown" msgstr "Il canvas da mostrare" #: src/adg/adg-gtk-area.c:588 msgid "" "The factor to use while zooming in and out (usually with the mouse wheel)" msgstr "" "Il fattore da usare per ingrandire (di solito con la rotella del mouse) " "avanti e indietro" #: src/adg/adg-gtk-area.c:594 msgid "Autozoom" msgstr "Autozoom" #: src/adg/adg-gtk-area.c:595 msgid "" "When enabled, automatically adjust the zoom in global space at every size " "allocation" msgstr "" "Quando abilitato, corregge automaticamente l'ingrandimento in global space " "ad ogni nuovo cambiamento di dimensione" #: src/adg/adg-gtk-area.c:601 msgid "Render Map" msgstr "Mappa di Render" #: src/adg/adg-gtk-area.c:602 msgid "The transformation to be applied on the canvas before rendering it" msgstr "La trasformazione da applicare al canvas prima del rendering" #: src/adg/adg-gtk-layout.c:412 msgid "Horizontal adjustment" msgstr "Regolazione orizzontale" #: src/adg/adg-gtk-layout.c:413 msgid "" "The GtkAdjustment that determines the values of the horizontal position for " "this viewport" msgstr "" "Il GtkAdjustment che specifica il valore della posizione orizzontale di " "questa viewport" #: src/adg/adg-gtk-layout.c:419 msgid "Vertical adjustment" msgstr "Regolazione verticale" #: src/adg/adg-gtk-layout.c:420 msgid "" "The GtkAdjustment that determines the values of the vertical position for " "this viewport" msgstr "" "Il GtkAdjustment che specifica il valore della posizione verticale di questa " "viewport" #: src/adg/adg-hatch.c:90 msgid "Fill Dress" msgstr "Vestito Riempimento" #: src/adg/adg-hatch.c:91 msgid "The dress to use for filling this entity" msgstr "Il vestito da usare per riempire questa entità" #: src/adg/adg-ldim.c:136 msgid "Direction" msgstr "Direzione" #: src/adg/adg-ldim.c:137 msgid "The inclination angle of the extension lines" msgstr "L'angolo di inclinazione delle linee di estensione" #: src/adg/adg-line-style.c:97 msgid "The color dress to bind to this line style" msgstr "Il vestito colore da allegare a questo stile di linea" #: src/adg/adg-line-style.c:103 msgid "Line Width" msgstr "Spessore Linea" #: src/adg/adg-line-style.c:104 msgid "The line thickness in device unit" msgstr "Lo spessore della linea in unità device" #: src/adg/adg-line-style.c:109 msgid "Line Cap" msgstr "Terminazione Linea" #: src/adg/adg-line-style.c:110 msgid "The line cap mode" msgstr "La modalità di terminazione della linea" #: src/adg/adg-line-style.c:116 msgid "Line Join" msgstr "Unione Linee" #: src/adg/adg-line-style.c:117 msgid "The line join mode" msgstr "La modalità di unione delle linee" #: src/adg/adg-line-style.c:123 msgid "Miter Limit" msgstr "Limite Punta" #: src/adg/adg-line-style.c:124 msgid "Whether the lines should be joined with a bevel instead of a miter" msgstr "" "Quando le linee devono essere unite da un trapezio invece che da un triangolo" #: src/adg/adg-line-style.c:130 msgid "Antialiasing Mode" msgstr "Modalità di Antialiasing" #: src/adg/adg-line-style.c:131 msgid "Type of antialiasing to do when rendering lines" msgstr "Tipo di antialiasing da applicare durante il rendering delle linee" #: src/adg/adg-line-style.c:137 msgid "Dash Pattern" msgstr "Pattern Tratteggio" #: src/adg/adg-line-style.c:138 msgid "The dash pattern to be used while stroking a path" msgstr "Il pattern del tratteggio da usare durante il disegno di un path" #: src/adg/adg-logo.c:93 src/adg/adg-projection.c:95 msgid "Symbol Dress" msgstr "Vestito Simbolo" #: src/adg/adg-logo.c:94 msgid "The line dress to use for rendering the symbol of the logo" msgstr "Il vestito da usare per il disegno delle linee del simbolo del logo" #: src/adg/adg-logo.c:100 msgid "Screen Dress" msgstr "Vestito Schermo" #: src/adg/adg-logo.c:101 msgid "The line dress to use for rendering the screen shape around the logo" msgstr "" "Il vestito da usare per il disegno delle linee della forma attorno al logo" #: src/adg/adg-logo.c:108 msgid "The line dress to use for rendering the frame" msgstr "Il vestito da usare per disegnare le linee del riquadro" #: src/adg/adg-marker.c:134 src/adg/adg-stroke.c:109 msgid "Trail" msgstr "Scia" #: src/adg/adg-marker.c:135 msgid "The subject AdgTrail for this marker" msgstr "L'AdgTrail soggetto per questo indicatore" #: src/adg/adg-marker.c:141 msgid "Segment Index" msgstr "Indice del Segmento" #: src/adg/adg-marker.c:142 msgid "" "The segment on trail where this marker should be applied (where 0 means " "undefined segment, 1 the first segment and so on)" msgstr "" "Il segmento dell'AdgTrail dove questo indicatore dovrebbe essere applicato " "(dove 0 significa nessun segmento, 1 il primo segmento e così via)" #: src/adg/adg-marker.c:149 msgid "" "The position ratio inside the segment where to put the marker (0 means the " "start point while 1 means the end point)" msgstr "" "Il ratio di posizione all'interno del segmento dove inserire l'indicatore (0 " "significa punto iniziale mentre 1 significa punto finale)" #: src/adg/adg-marker.c:155 msgid "Marker Size" msgstr "Dimensione Indicatore" #: src/adg/adg-marker.c:156 msgid "The size (in global space) of the marker" msgstr "La dimensione (in global space) dell'indicatore" #: src/adg/adg-marker.c:162 msgid "Model" msgstr "Modello" #: src/adg/adg-marker.c:163 msgid "A general purpose model usable by the marker implementations" msgstr "Un modello ad uso generale usato dall'implementazione dell'indicatore" #: src/adg/adg-marker.c:754 #, c-format msgid "%s: 'create_model' method not implemented for type '%s'" msgstr "%s: 'create_model' non implementato per il tipo '%s'" #: src/adg/adg-matrix.c:163 #, c-format msgid "%s: normalization of anamorphic matrices not supported" msgstr "%s: normalizzazione di matrici anamorfiche non supportata" #: src/adg/adg-model.c:186 msgid "Dependency" msgstr "Dipendenza" #: src/adg/adg-model.c:187 msgid "" "Can be used to add a new dependency from this model (this entity will be " "invalidated on model changed)" msgstr "" "Può essere usato per aggiungere una nuova dipendenza da questo modello " "(questa entità sarà invalidata quando cambia il modello)" #: src/adg/adg-model.c:679 #, c-format msgid "" "%s: attempting to remove the nonexistent dependency on the entity with type " "%s from a model of type %s" msgstr "" "%s: si è provato ad eliminare un'inesistente dipendenza da un'entità di tipo " "%s in un modello di tipo %s" #: src/adg/adg-model.c:715 #, c-format msgid "%s: attempting to remove nonexistent '%s' named pair" msgstr "%s: si è provato ad eliminare l'inesistente named pair '%s'" #: src/adg/adg-pango-style.c:93 src/adg/adg-ruled-fill.c:116 msgid "Spacing" msgstr "Distanza" #: src/adg/adg-pango-style.c:94 msgid "Amount of spacing between lines on multiline text" msgstr "Quantità di spazio tra le linee di un testo multilinea" #: src/adg/adg-pango-style.c:207 #, c-format msgid "Unhandled slant value (%d)" msgstr "Valore di slant inatteso (%d)" #: src/adg/adg-pango-style.c:220 #, c-format msgid "Unhandled weight value (%d)" msgstr "Valore di weight inatteso (%d)" #: src/adg/adg-path.c:424 #, c-format msgid "%s: null pair caught while parsing arguments" msgstr "%s: rilevato un named pair nullo durante l'analisi degli argomenti" #: src/adg/adg-path.c:1044 #, c-format msgid "%s: the axis of the reflection is not known" msgstr "%s: l'asse di riflessione non è conosciuta" #: src/adg/adg-path.c:1273 #, c-format msgid "%s: a '%s' operation is still active while clearing the path" msgstr "%s: operazione '%s' ancora attiva durante l'azzeramento del path" #: src/adg/adg-path.c:1292 #, c-format msgid "%s: requested a '%s' operation on a path without current primitive" msgstr "%s: richiesta l'operazione '%s' su un path senza primitiva attiva" #: src/adg/adg-path.c:1299 #, c-format msgid "%s: requested a '%s' operation while a '%s' operation was active" msgstr "%s: richiesta l'operazione '%s' mentre l'operazione '%s' era in corso" #: src/adg/adg-path.c:1324 #, c-format msgid "%s: %d path operation not recognized" msgstr "%s: l'operazione %d non è conosciuta" #: src/adg/adg-path.c:1428 #, c-format msgid "" "%s: first chamfer delta of %lf is greather than the available %lf length" msgstr "" "%s: il primo delta dello smusso %lf è maggiore dello spazio disponibile %lf" #: src/adg/adg-path.c:1437 #, c-format msgid "" "%s: second chamfer delta of %lf is greather than the available %lf length" msgstr "" "%s: il secondo delta dello smusso %lf è maggiore dello spazio disponibile %lf" #: src/adg/adg-path.c:1472 #, c-format msgid "%s: fillet with radius of %lf is not applicable here" msgstr "%s: un raccordo di raggio %lf non è eseguibile ora" #: src/adg/adg-point.c:325 #, c-format msgid "%s: trying to get a pair from an undefined point" msgstr "%s: si è provato ad ottenere le coordinate da un punto indefinito" #: src/adg/adg-projection.c:96 msgid "The line dress to use for rendering the views of the projection" msgstr "" "Il vestito linea da usare per il rendering delle viste della proiezione" #: src/adg/adg-projection.c:102 msgid "Axis Dress" msgstr "Vestito Assi" #: src/adg/adg-projection.c:103 msgid "The line dress to use for rendering the axis of the projection scheme" msgstr "Il vestito linea da usare per il rendering degli assi della proiezione" #: src/adg/adg-projection.c:109 src/adg/adg-title-block.c:147 msgid "Projection Scheme" msgstr "Proiezione Ortografica" #: src/adg/adg-projection.c:110 msgid "The projection scheme to be represented" msgstr "Il tipo di proiezione ortografica da adottare" #: src/adg/adg-ruled-fill.c:110 msgid "Dress to be used for rendering the lines" msgstr "Vestito da usare per disegnare le linee" #: src/adg/adg-ruled-fill.c:117 msgid "The spacing in global spaces between the lines" msgstr "La distanza nello spazio globale tra le linee" #: src/adg/adg-ruled-fill.c:123 msgid "Angle" msgstr "Angolo" #: src/adg/adg-ruled-fill.c:124 msgid "The angle (in radians) of the lines" msgstr "L'angolo (in radianti) delle linee" #: src/adg/adg-stroke.c:103 src/adg/adg-table.c:145 msgid "The dress to use for stroking this entity" msgstr "Il vestito da usare per tracciare questa entità" #: src/adg/adg-stroke.c:110 msgid "The trail to be stroked" msgstr "La scia da tracciare" #: src/adg/adg-style.c:222 #, c-format msgid "%s: 'apply' method not implemented for type '%s'" msgstr "%s: metodo 'arrange' non implementato per il tipo '%s'" #: src/adg/adg-table.c:144 msgid "Table Dress" msgstr "Vestito Tabella" #: src/adg/adg-table.c:152 msgid "" "If enabled, a frame using the proper dress found in this table style will be " "drawn around the table extents" msgstr "" "Se abilitato, un riquadro attorno alla tabella verrà disegnato usando il " "vestito specificato dallo stile della tabella stessa" #: src/adg/adg-table-style.c:94 msgid "Fallback color dress, used when no specific dresses are selected" msgstr "" "Vestito colore implicito, da usare quando nessun altro vestito è stato " "specificato" #: src/adg/adg-table-style.c:100 msgid "Grid Dress" msgstr "Vestito Griglia" #: src/adg/adg-table-style.c:101 msgid "Line dress to use while rendering the grid of the table" msgstr "Vestito da usare per tracciare la griglia della tabella" #: src/adg/adg-table-style.c:108 msgid "Line dress to use while drawing the table frame" msgstr "Vestito da usare per tracciare il riquadro della tabella" #: src/adg/adg-table-style.c:114 msgid "Title Dress" msgstr "Vestito Titolo" #: src/adg/adg-table-style.c:115 msgid "Font dress to use for titles" msgstr "Vestito del font da usare per il titolo" #: src/adg/adg-table-style.c:122 msgid "Font dress to use for values inside the cells" msgstr "" "Vestito del font da usare per scrivere il valore all'interno delle celle" #: src/adg/adg-table-style.c:128 msgid "Row Height" msgstr "Altezza Riga" #: src/adg/adg-table-style.c:129 +#, fuzzy msgid "" -"The fallback row height when not explicitely specified while creating a new " +"The fallback row height when not explicitly specified while creating a new " "row" msgstr "" "L'altezza implicita della riga da usare quando non è stata specificata " "un'altezza durante la creazione di una nuova riga" #: src/adg/adg-table-style.c:135 msgid "Cell Padding" msgstr "Spaziatura Celle" #: src/adg/adg-table-style.c:136 msgid "How much space from the bounding box must left inside every cell" msgstr "Quanto spazio lasciare vuoto all'interno di ogni cella" #: src/adg/adg-table-style.c:142 msgid "Cell Spacing" msgstr "Distanza Celle" #: src/adg/adg-table-style.c:143 msgid "How much space to left between the cells" msgstr "Quanto spazio lasciare vuoto tra le celle" #: src/adg/adg-textual.c:110 msgid "Text" msgstr "Testo" #: src/adg/adg-textual.c:111 msgid "The text associated to this entity" msgstr "Il testo associato a questa entità" #: src/adg/adg-textual.c:117 msgid "Font Dress" msgstr "Vestito del Font" #: src/adg/adg-textual.c:118 msgid "The font dress to use for rendering this text" msgstr "Il vestito del font da usare per disegnare questo testo" #: src/adg/adg-textual.c:162 #, c-format msgid "%s: 'set_font_dress' method not implemented for type '%s'" msgstr "%s: metodo 'set_font_dress' non implementato per il tipo '%s'" #: src/adg/adg-textual.c:191 #, c-format msgid "%s: 'get_font_dress' method not implemented for type '%s'" msgstr "%s: metodo 'get_font_dress' non implementato per il tipo '%s'" #: src/adg/adg-textual.c:222 #, c-format msgid "%s: 'set_text' method not implemented for type '%s'" msgstr "%s: metodo 'set_text' non implementato per il tipo '%s'" #: src/adg/adg-textual.c:281 #, c-format msgid "%s: 'dup_text' method not implemented for type '%s'" msgstr "%s: metodo 'dup_text' non implementato per il tipo '%s'" #: src/adg/adg-title-block.c:98 msgid "Title" msgstr "Titolo" #: src/adg/adg-title-block.c:99 msgid "A descriptive title of the drawing" msgstr "Un titolo descrittivo del disegno" #: src/adg/adg-title-block.c:105 msgid "Drawing Name" msgstr "Nome Disegno" #: src/adg/adg-title-block.c:106 msgid "" "The name of the drawing: the ADG canvas does not make any assumtpion on this " "text string" msgstr "" "Il nome del disegno: il canvas ADG non avanza alcuna ipotesi sul significato " "di questa stringa" #: src/adg/adg-title-block.c:112 msgid "Media Size" msgstr "Dimensione del Supporto" #: src/adg/adg-title-block.c:113 msgid "" "The media size to be used to print the drawing, usually something like " "\"A3\" or \"Letter\"" msgstr "" "La dimensione del supporto usato per stampare il disegno, solitamente una " "stringa tipo \"A3\" o \"Letter\"" #: src/adg/adg-title-block.c:119 msgid "Scale" msgstr "Scala" #: src/adg/adg-title-block.c:120 msgid "The scale of the drawing, if it makes sense" msgstr "La scala del disegno, se significativa" #: src/adg/adg-title-block.c:126 msgid "Author" msgstr "Autore" #: src/adg/adg-title-block.c:127 msgid "Name and last name of the author of the drawing" msgstr "Nome e cognome dell'autore del disegno" #: src/adg/adg-title-block.c:133 msgid "Date" msgstr "Data" #: src/adg/adg-title-block.c:134 msgid "" "The date this drawing has been generated: setting it to an empty string will " "fallback to today in the preferred representation for the current locale" msgstr "" "La data di generazione disegno: impostandola ad una stringa vuota, la data " "verrà inizializzata automaticamente ad oggi nel formato privilegiato dalle " "impostazioni di localizzazione correnti" #: src/adg/adg-title-block.c:140 msgid "Logo" msgstr "Logo" #: src/adg/adg-title-block.c:141 msgid "" "An entity to be displayed in the title block as the logo of the owner: the " "containing cell has a 1:1 ratio" msgstr "" "L'entità da mostrare nel riquadro delle iscrizioni come logo del " "proprietario: la cella contenitrice ha un ratio 1:1" #: src/adg/adg-title-block.c:148 msgid "" "The entity usually reserved to identify the projection scheme adopted by " "this drawing" msgstr "" "Entità solitamente riservata per identificare il tipo di proiezione " "ortografica usata in questo disegno" #: src/adg/adg-title-block.c:178 msgid "TITLE" msgstr "TITOLO" #: src/adg/adg-title-block.c:183 msgid "SIZE" msgstr "FOGLIO" #: src/adg/adg-title-block.c:184 msgid "SCALE" msgstr "SCALA" #: src/adg/adg-title-block.c:185 msgid "DRAWING" msgstr "DISEGNO" #: src/adg/adg-title-block.c:190 msgid "AUTHOR" msgstr "AUTORE" #: src/adg/adg-title-block.c:191 msgid "DATE" msgstr "DATA" #: src/adg/adg-toy-text.c:287 #, c-format msgid "Unable to build glyphs (cairo message: %s)" msgstr "Non riesco a generare i glifi (messaggio da cairo: %s)" #: src/adg/adg-trail.c:133 msgid "Max Angle" msgstr "Angolo Massimo" #: src/adg/adg-trail.c:134 msgid "" "Max arc angle to approximate with a single Bezier curve: check " "adg_trail_set_max_angle() for details" msgstr "" "Massimo angolo di arco da approssimare con una singola linea di Bézier: " "consultare adg_trail_set_max_angle() per dettagli" #: src/adg/adg-trail.c:322 #, c-format msgid "" "%s: you cannot access the path from the callback you provided to build it" msgstr "%s: accesso al path non consentito dalla funzione che deve definirlo" #: src/adg/adg-trail.c:396 #, c-format msgid "%s: requested undefined segment for type '%s'" msgstr "%s: è stato richiesto un segmento indefinito per il tipo '%s'" #: src/adg/adg-trail.c:473 #, c-format msgid "%s: invalid path data" msgstr "%s: dati del path non validi" #: src/adg/adg-trail.c:559 #, c-format msgid "%s: callback not defined for instance of type '%s'" msgstr "%s: callback non definita per un'istanza di tipo '%s'" diff --git a/src/adg/adg-canvas.c b/src/adg/adg-canvas.c index f03bc3d1..22e66fbb 100644 --- a/src/adg/adg-canvas.c +++ b/src/adg/adg-canvas.c @@ -1,1929 +1,1929 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-canvas * @short_description: The drawing container * * The canvas is the toplevel entity of an ADG drawing. It can be * bound to a GTK+ widget, such as #AdgGtkArea, or manually rendered * to a custom surface. * * Tipically, the canvas contains the description and properties of * the media used, such as such as size (if relevant), margins, * border and paddings. This approach clearly follows the block model * of the CSS specification. * * The paddings specify the distance between the entities contained * by the canvas and the border. The margins specify the distance * between the canvas border and the media extents. * - * The canvas (hence the media) size can be explicitely specified + * The canvas (hence the media) size can be explicitly specified * by directly writing to the #AdgCanvas:size property or using any * valid setter, such as adg_canvas_set_size(), * adg_canvas_set_size_explicit() or the convenient - * adg_canvas_set_paper() GTK+ wrapper. You can also set explicitely + * adg_canvas_set_paper() GTK+ wrapper. You can also set explicitly * only one dimension and let the other one be computed automatically. * This can be done by setting it to 0. * The ratio between the media unit (typically points on printing * surfaces and pixels on video surfaces) can be changed with * adg_canvas_set_factor(). * * By default either width and height are autocalculated, i.e. they * are initially set to 0. In this case the arrange() phase is executed. * Margins and paddings are then added to the extents to get the * border coordinates and the final bounding box. * - * Instead, when the size is explicitely set, the final bounding + * Instead, when the size is explicitly set, the final bounding * box is forcibly set to this value without taking the canvas * extents into account. The margins are then subtracted to get * the coordinates of the border. In this case the paddings are * simply ignoredby the arrange phase. They are still used by * adg_canvas_autoscale() though, if called. * * Since: 1.0 **/ /** * AdgCanvas: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * ADG_CANVAS_ERROR: * * Error domain for canvas processing. Errors in this domain will be from the * #AdgCanvasError enumeration. See #GError for information on error domains. * * Since: 1.0 **/ /** * AdgCanvasError: * @ADG_CANVAS_ERROR_SURFACE: Invalid surface type. * @ADG_CANVAS_ERROR_CAIRO: The underlying cairo library returned an error. * * Error codes returned by #AdgCanvas methods. * * Since: 1.0 **/ #include "adg-internal.h" #include #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include "adg-style.h" #include "adg-color-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include #include "adg-canvas-private.h" #ifdef CAIRO_HAS_PS_SURFACE #include #endif #ifdef CAIRO_HAS_PDF_SURFACE #include #endif #ifdef CAIRO_HAS_SVG_SURFACE #include #endif #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_canvas_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_canvas_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgCanvas, adg_canvas, ADG_TYPE_CONTAINER) enum { PROP_0, PROP_SIZE, PROP_FACTOR, PROP_SCALES, PROP_BACKGROUND_DRESS, PROP_FRAME_DRESS, PROP_TITLE_BLOCK, PROP_TOP_MARGIN, PROP_RIGHT_MARGIN, PROP_BOTTOM_MARGIN, PROP_LEFT_MARGIN, PROP_HAS_FRAME, PROP_TOP_PADDING, PROP_RIGHT_PADDING, PROP_BOTTOM_PADDING, PROP_LEFT_PADDING }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_apply_paddings (AdgCanvas *canvas, CpmlExtents *extents); static void _adg_update_margin (AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin); /** * adg_canvas_error_quark: * * Registers an error quark specific for #AdgCanvas. * * Returns: The error quark used for #AdgCanvas errors. * * Since: 1.0 **/ GQuark adg_canvas_error_quark(void) { static GQuark q; if G_UNLIKELY (q == 0) q = g_quark_from_static_string("adg-canvas-error-quark"); return q; } static void adg_canvas_class_init(AdgCanvasClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = g_param_spec_boxed("size", P_("Canvas Size"), P_("The size set on this canvas: use 0 to have an automatic dimension based on the canvas extents"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SIZE, param); param = g_param_spec_double("factor", P_("Factor"), P_("Global space units per point (1 point == 1/72 inch)"), 0, G_MAXDOUBLE, 1, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FACTOR, param); param = g_param_spec_boxed("scales", P_("Valid Scales"), P_("List of scales to be tested while autoscaling the drawing"), G_TYPE_STRV, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_SCALES, param); param = adg_param_spec_dress("background-dress", P_("Background Dress"), P_("The color dress to use for the canvas background"), ADG_DRESS_COLOR_BACKGROUND, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BACKGROUND_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("Line dress to use while drawing the frame around the canvas"), ADG_DRESS_LINE_FRAME, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); param = g_param_spec_object("title-block", P_("Title Block"), P_("The title block to assign to this canvas"), ADG_TYPE_TITLE_BLOCK, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TITLE_BLOCK, param); param = g_param_spec_double("top-margin", P_("Top Margin"), P_("The margin (in global space) to leave above the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_MARGIN, param); param = g_param_spec_double("right-margin", P_("Right Margin"), P_("The margin (in global space) to leave empty at the right of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_MARGIN, param); param = g_param_spec_double("bottom-margin", P_("Bottom Margin"), P_("The margin (in global space) to leave empty below the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_MARGIN, param); param = g_param_spec_double("left-margin", P_("Left Margin"), P_("The margin (in global space) to leave empty at the left of the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_MARGIN, param); param = g_param_spec_boolean("has-frame", P_("Has Frame Flag"), P_("If enabled, a frame using the frame dress will be drawn around the canvas extents, taking into account the margins"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param); param = g_param_spec_double("top-padding", P_("Top Padding"), P_("The padding (in global space) to leave empty above between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TOP_PADDING, param); param = g_param_spec_double("right-padding", P_("Right Padding"), P_("The padding (in global space) to leave empty at the right between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_RIGHT_PADDING, param); param = g_param_spec_double("bottom-padding", P_("Bottom Padding"), P_("The padding (in global space) to leave empty below between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BOTTOM_PADDING, param); param = g_param_spec_double("left-padding", P_("Left Padding"), P_("The padding (in global space) to leave empty at the left between the drawing and the frame"), -G_MAXDOUBLE, G_MAXDOUBLE, 15, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEFT_PADDING, param); } static void adg_canvas_init(AdgCanvas *canvas) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); const gchar *scales[] = { "10:1", "5:1", "3:1", "2:1", "1:1", "1:2", "1:3", "1:5", "1:10", NULL }; data->size.x = 0; data->size.y = 0; data->factor = 1; data->scales = g_strdupv((gchar **) scales); data->background_dress = ADG_DRESS_COLOR_BACKGROUND; data->frame_dress = ADG_DRESS_LINE_FRAME; data->title_block = NULL; data->top_margin = 15; data->right_margin = 15; data->bottom_margin = 15; data->left_margin = 15; data->has_frame = TRUE; data->top_padding = 15; data->right_padding = 15; data->bottom_padding = 15; data->left_padding = 15; } static void _adg_dispose(GObject *object) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); if (data->title_block) { g_object_unref(data->title_block); data->title_block = NULL; } if (data->scales != NULL) { g_strfreev(data->scales); data->scales = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) object); switch (prop_id) { case PROP_SIZE: g_value_set_boxed(value, &data->size); break; case PROP_FACTOR: g_value_set_double(value, data->factor); break; case PROP_SCALES: g_value_set_boxed(value, data->scales); break; case PROP_BACKGROUND_DRESS: g_value_set_enum(value, data->background_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; case PROP_TITLE_BLOCK: g_value_set_object(value, data->title_block); break; case PROP_TOP_MARGIN: g_value_set_double(value, data->top_margin); break; case PROP_RIGHT_MARGIN: g_value_set_double(value, data->right_margin); break; case PROP_BOTTOM_MARGIN: g_value_set_double(value, data->bottom_margin); break; case PROP_LEFT_MARGIN: g_value_set_double(value, data->left_margin); break; case PROP_HAS_FRAME: g_value_set_boolean(value, data->has_frame); break; case PROP_TOP_PADDING: g_value_set_double(value, data->top_padding); break; case PROP_RIGHT_PADDING: g_value_set_double(value, data->right_padding); break; case PROP_BOTTOM_PADDING: g_value_set_double(value, data->bottom_padding); break; case PROP_LEFT_PADDING: g_value_set_double(value, data->left_padding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgCanvas *canvas = (AdgCanvas *) object; AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); AdgTitleBlock *title_block; gdouble factor; switch (prop_id) { case PROP_SIZE: cpml_pair_copy(&data->size, g_value_get_boxed(value)); break; case PROP_FACTOR: factor = g_value_get_double(value); g_return_if_fail(factor > 0); data->factor = factor; break; case PROP_SCALES: g_strfreev(data->scales); data->scales = g_value_dup_boxed(value); break; case PROP_BACKGROUND_DRESS: data->background_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; case PROP_TITLE_BLOCK: title_block = g_value_get_object(value); if (title_block) { g_object_ref(title_block); adg_entity_set_parent((AdgEntity *) title_block, (AdgEntity *) canvas); } if (data->title_block) g_object_unref(data->title_block); data->title_block = title_block; break; case PROP_TOP_MARGIN: _adg_update_margin(canvas, &data->top_margin, &data->size.y, g_value_get_double(value)); break; case PROP_RIGHT_MARGIN: _adg_update_margin(canvas, &data->right_margin, &data->size.x, g_value_get_double(value)); break; case PROP_BOTTOM_MARGIN: _adg_update_margin(canvas, &data->bottom_margin, &data->size.y, g_value_get_double(value)); break; case PROP_LEFT_MARGIN: _adg_update_margin(canvas, &data->left_margin, &data->size.x, g_value_get_double(value)); break; case PROP_HAS_FRAME: data->has_frame = g_value_get_boolean(value); break; case PROP_TOP_PADDING: data->top_padding = g_value_get_double(value); break; case PROP_RIGHT_PADDING: data->right_padding = g_value_get_double(value); break; case PROP_BOTTOM_PADDING: data->bottom_padding = g_value_get_double(value); break; case PROP_LEFT_PADDING: data->left_padding = g_value_get_double(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_canvas_new: * * Creates a new empty canvas object. * * Returns: (transfer full): the newly created canvas. * * Since: 1.0 **/ AdgCanvas * adg_canvas_new(void) { return g_object_new(ADG_TYPE_CANVAS, NULL); } /** * adg_canvas_set_size: * @canvas: an #AdgCanvas * @size: (transfer none): the new size for the canvas * * Sets a specific size on @canvas. The x and/or y * component of @size can be set to 0, in which case * the exact value will be autocalculated, that is the * size component returned by adg_entity_get_extents() * on @canvas will be used instead. * * Since: 1.0 **/ void adg_canvas_set_size(AdgCanvas *canvas, const CpmlPair *size) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(size != NULL); g_object_set(canvas, "size", size, NULL); } /** * adg_canvas_set_size_explicit: * @canvas: an #AdgCanvas * @x: the new width of the canvas or 0 to reset * @y: the new height of the canvas or 0 to reset * * A convenient function to set the size of @canvas using * explicit coordinates. Check adg_canvas_set_size() for * further details. * * Since: 1.0 **/ void adg_canvas_set_size_explicit(AdgCanvas *canvas, gdouble x, gdouble y) { CpmlPair size; size.x = x; size.y = y; adg_canvas_set_size(canvas, &size); } /** * adg_canvas_get_size: * @canvas: an #AdgCanvas * * Gets the specific size set on @canvas. The x and/or y * components of the returned #CpmlPair could be 0, in which * case the size returned by adg_entity_get_extents() on @canvas * will be used instead. * * Returns: (transfer none): the explicit size set on this canvas or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_canvas_get_size(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return &data->size; } /** * adg_canvas_set_factor: * @canvas: an #AdgCanvas * @factor: the new factor: must be greater than 0 * * The ADG library is intentionally unit agnostic, i.e. the global space is * represented in whatever unit you want. There are a couple of cairo APIs - * that explicitely requires points though, most notably + * that explicitly requires points though, most notably * cairo_pdf_surface_set_size() and cairo_ps_surface_set_size(). * * On PDF and postscript surfaces, the AdgCanvas:factor property will be the * number typography points per global space units: * https://en.wikipedia.org/wiki/Point_(typography) * * For other surfaces, the factor will be used to scale the final surface to * the specific number of pixels. * * As an example, if you are working in millimeters you will set a factor of * 2.83465 on PDF and postscript surfaces (1 mm = 2.83465 points) so the * drawing will show the correct scale on paper. For X11 and PNG surfaces you * will set it depending on the resolution you want to get, e.g. if the * drawing is 100x200 mm and you want a 1000x2000 image, just set it to 10. * * Since: 1.0 **/ void adg_canvas_set_factor(AdgCanvas *canvas, double factor) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(factor > 0); g_object_set(canvas, "factor", factor, NULL); } /** * adg_canvas_get_factor: * @canvas: an #AdgCanvas * * Gets the current factor of @canvas. See adg_canvas_set_factor() to learn * what a factor is in this context. * * Returns: the canvas factor or 0 on error. * * Since: 1.0 **/ double adg_canvas_get_factor(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->factor; } /** * adg_canvas_set_scales: * @canvas: an #AdgCanvas * @...: NULL terminated list of strings * * Sets the scales allowed by @canvas. Every scale identifies a * specific factor to be applied to the local matrix of @canvas. * When adg_canvas_autoscale() will be called, the greatest * scale that can render every entity inside a box of * #AdgCanvas:size dimensions will be applied. The drawing will * be centered inside that box. * * Every scale should be expressed with a string in the form of * "x:y", where x and y are positive integers that identifies * numerator an denominator of a fraction. That string itself * will be put into the title block when used. * * Since: 1.0 **/ void adg_canvas_set_scales(AdgCanvas *canvas, ...) { va_list var_args; va_start(var_args, canvas); adg_canvas_set_scales_valist(canvas, var_args); va_end(var_args); } /** * adg_canvas_set_scales_valist: * @canvas: an #AdgCanvas * @var_args: NULL terminated list of strings * * Vararg variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_valist(AdgCanvas *canvas, va_list var_args) { gchar **scales; const gchar *scale; gint n; g_return_if_fail(ADG_IS_CANVAS(canvas)); scales = NULL; n = 0; while ((scale = va_arg(var_args, const gchar *)) != NULL) { ++n; scales = g_realloc(scales, sizeof(const gchar *) * (n + 1)); scales[n-1] = g_strdup(scale); scales[n] = NULL; } g_object_set(canvas, "scales", scales, NULL); g_strfreev(scales); } /** * adg_canvas_set_scales_array: (rename-to adg_canvas_set_scales) * @canvas: an #AdgCanvas * @scales: (array zero-terminated=1) (allow-none): NULL terminated array of scales * * Array variant of adg_canvas_set_scales(). * * Since: 1.0 **/ void adg_canvas_set_scales_array(AdgCanvas *canvas, gchar **scales) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "scales", scales, NULL); } /** * adg_canvas_get_scales: * @canvas: an #AdgCanvas * * Gets the list of scales set on @canvas: check adg_canvas_set_scales() * to get an idea of what scales are supposed to be. * * If no scales are set, NULL is returned. * * Returns: (element-type utf8) (transfer none): the NULL terminated list of valid scales. * * Since: 1.0 **/ gchar ** adg_canvas_get_scales(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->scales; } /** * adg_canvas_autoscale: * @canvas: an #AdgCanvas * * Applies one scale per time, in the order they have been provided * in the adg_canvas_set_scales() call, until the drawing can be * entirely contained into the current paper. If successful, the * scale of the title block is changed accordingly and the drawing * is centered inside the paper. * * The paddings are taken into account while computing the drawing * extents. * * Since: 1.0 **/ void adg_canvas_autoscale(AdgCanvas *canvas) { AdgCanvasPrivate *data; gchar **p_scale; AdgEntity *entity; cairo_matrix_t map; CpmlExtents extents; AdgTitleBlock *title_block; CpmlPair delta; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(_ADG_OLD_ENTITY_CLASS->arrange != NULL); data = adg_canvas_get_instance_private(canvas); entity = (AdgEntity *) canvas; title_block = data->title_block; /* Manually calling the arrange() method instead of emitting the "arrange" * signal does not invalidate the global matrix: let's do it right now */ adg_entity_global_changed(entity); for (p_scale = data->scales; p_scale != NULL && *p_scale != NULL; ++p_scale) { const gchar *scale = *p_scale; gdouble factor = adg_scale_factor(scale); if (factor <= 0) continue; cairo_matrix_init_scale(&map, factor, factor); adg_entity_set_local_map(entity, &map); adg_entity_local_changed(entity); /* Arrange the entities inside the canvas, but not the canvas itself, * just to get the bounding box of the drawing without the paper */ _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* Just in case @canvas is empty */ if (! extents.is_defined) return; _adg_apply_paddings(canvas, &extents); if (title_block != NULL) adg_title_block_set_scale(title_block, scale); /* Bail out if paper size is not specified or invalid */ if (data->size.x <= 0 || data->size.y <= 0) break; /* If the drawing extents are fully contained inside the paper size, * center the drawing in the paper and bail out */ delta.x = data->size.x - extents.size.x; delta.y = data->size.y - extents.size.y; if (delta.x >= 0 && delta.y >= 0) { cairo_matrix_t transform; cairo_matrix_init_translate(&transform, delta.x / 2 - extents.org.x, delta.y / 2 - extents.org.y); adg_entity_transform_local_map(entity, &transform, ADG_TRANSFORM_AFTER); break; } } } /** * adg_canvas_set_background_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets a new background dress for rendering @canvas: the new * dress must be a color dress. * * Since: 1.0 **/ void adg_canvas_set_background_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "background-dress", dress, NULL); } /** * adg_canvas_get_background_dress: * @canvas: an #AdgCanvas * * Gets the background dress to be used in rendering @canvas. * * Returns: the current background dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_background_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->background_dress; } /** * adg_canvas_set_frame_dress: * @canvas: an #AdgCanvas * @dress: the new #AdgDress to use * * Sets the #AdgCanvas:frame-dress property of @canvas to @dress: * the new dress must be a line dress. * * Since: 1.0 **/ void adg_canvas_set_frame_dress(AdgCanvas *canvas, AdgDress dress) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "frame-dress", dress, NULL); } /** * adg_canvas_get_frame_dress: * @canvas: an #AdgCanvas * * Gets the frame dress to be used in rendering the border of @canvas. * * Returns: the current frame dress. * * Since: 1.0 **/ AdgDress adg_canvas_get_frame_dress(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), ADG_DRESS_UNDEFINED); data = adg_canvas_get_instance_private(canvas); return data->frame_dress; } /** * adg_canvas_set_title_block: * @canvas: an #AdgCanvas * @title_block: (transfer full): a title block * * Sets the #AdgCanvas:title-block property of @canvas to @title_block. * * Although a title block entity could be added to @canvas in the usual * way, that is using the adg_container_add() method, assigning a title * block with adg_canvas_set_title_block() is somewhat different: * * - @title_block will be automatically attached to the bottom right * corner of to the @canvas frame (this could be accomplished in the * usual way too, by resetting the right and bottom paddings); * - the @title_block boundary box is not taken into account while * computing the extents of @canvas. * * Since: 1.0 **/ void adg_canvas_set_title_block(AdgCanvas *canvas, AdgTitleBlock *title_block) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(title_block == NULL || ADG_IS_TITLE_BLOCK(title_block)); g_object_set(canvas, "title-block", title_block, NULL); } /** * adg_canvas_get_title_block: * @canvas: an #AdgCanvas * * Gets the #AdgTitleBlock object of @canvas: check * adg_canvas_set_title_block() for details. * * The returned entity is owned by @canvas and should not be * modified or freed. * * Returns: (transfer none): the title block object or NULL. * * Since: 1.0 **/ AdgTitleBlock * adg_canvas_get_title_block(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); data = adg_canvas_get_instance_private(canvas); return data->title_block; } /** * adg_canvas_set_top_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the top margin of @canvas by setting #AdgCanvas:top-margin * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", value, NULL); } /** * adg_canvas_get_top_margin: * @canvas: an #AdgCanvas * * Gets the top margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_margin; } /** * adg_canvas_set_right_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the right margin of @canvas by setting * #AdgCanvas:right-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-margin", value, NULL); } /** * adg_canvas_get_right_margin: * @canvas: an #AdgCanvas * * Gets the right margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_margin; } /** * adg_canvas_set_bottom_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the bottom margin of @canvas by setting * #AdgCanvas:bottom-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-margin", value, NULL); } /** * adg_canvas_get_bottom_margin: * @canvas: an #AdgCanvas * * Gets the bottom margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_margin; } /** * adg_canvas_set_left_margin: * @canvas: an #AdgCanvas * @value: the new margin, in global space * * Changes the left margin of @canvas by setting * #AdgCanvas:left-margin to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_margin(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-margin", value, NULL); } /** * adg_canvas_get_left_margin: * @canvas: an #AdgCanvas * * Gets the left margin (in global space) of @canvas. * * Returns: the requested margin or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_margin(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_margin; } /** * adg_canvas_set_margins: * @canvas: an #AdgCanvas * @top: top margin, in global space * @right: right margin, in global space * @bottom: bottom margin, in global space * @left: left margin, in global space * * Convenient function to set all the margins at once. * * Since: 1.0 **/ void adg_canvas_set_margins(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-margin", top, "right-margin", right, "bottom-margin", bottom, "left-margin", left, NULL); } /** * adg_canvas_get_margins: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top margin or NULL * @right: (out) (nullable): where to store the right margin or NULL * @bottom: (out) (nullable): where to store the bottom margin or NULL * @left: (out) (nullable): where to store the left margin or NULL * * Convenient function to get all the margins at once. * * Since: 1.0 **/ void adg_canvas_get_margins(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_margin; } if (right != NULL) { *right = data->right_margin; } if (bottom != NULL) { *bottom = data->bottom_margin; } if (left != NULL) { *left = data->left_margin; } } /** * adg_canvas_apply_margins: * @canvas: an #AdgCanvas * @extents: where apply the margins * * A convenient function to apply the margins of @canvas to the * arbitrary #CpmlExtents struct @extents. "Apply" means @extents * are enlarged of the specific margin values. * * Since: 1.0 **/ void adg_canvas_apply_margins(AdgCanvas *canvas, CpmlExtents *extents) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(extents != NULL); if (extents->is_defined) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_margin; extents->org.y -= data->top_margin; extents->size.x += data->left_margin + data->right_margin; extents->size.y += data->top_margin + data->bottom_margin; } } /** * adg_canvas_switch_frame: * @canvas: an #AdgCanvas * @new_state: the new flag status * * Sets a new status on the #AdgCanvas:has-frame * property: TRUE means a border around * the canvas extents (less the margins) should be rendered. * * Since: 1.0 **/ void adg_canvas_switch_frame(AdgCanvas *canvas, gboolean new_state) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "has-frame", new_state, NULL); } /** * adg_canvas_has_frame: * @canvas: an #AdgCanvas * * Gets the current status of the #AdgCanvas:has-frame property, * that is whether a border around the canvas extents (less the * margins) should be rendered (TRUE) or not * (FALSE). * * Returns: the current status of the frame flag. * * Since: 1.0 **/ gboolean adg_canvas_has_frame(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); data = adg_canvas_get_instance_private(canvas); return data->has_frame; } /** * adg_canvas_set_top_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the top padding of @canvas by setting * #AdgCanvas:top-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_top_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", value, NULL); } /** * adg_canvas_get_top_padding: * @canvas: an #AdgCanvas * * Gets the top padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_top_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->top_padding; } /** * adg_canvas_set_right_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the right padding of @canvas by setting #AdgCanvas:right-padding * to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_right_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "right-padding", value, NULL); } /** * adg_canvas_get_right_padding: * @canvas: an #AdgCanvas * * Gets the right padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_right_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->right_padding; } /** * adg_canvas_set_bottom_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the bottom padding of @canvas by setting * #AdgCanvas:bottom-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_bottom_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "bottom-padding", value, NULL); } /** * adg_canvas_get_bottom_padding: * @canvas: an #AdgCanvas * * Gets the bottom padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_bottom_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->bottom_padding; } /** * adg_canvas_set_left_padding: * @canvas: an #AdgCanvas * @value: the new padding, in global space * * Changes the left padding of @canvas by setting * #AdgCanvas:left-padding to @value. Negative values are allowed. * * Since: 1.0 **/ void adg_canvas_set_left_padding(AdgCanvas *canvas, gdouble value) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "left-padding", value, NULL); } /** * adg_canvas_get_left_padding: * @canvas: an #AdgCanvas * * Gets the left padding (in global space) of @canvas. * * Returns: the requested padding or 0 on error. * * Since: 1.0 **/ gdouble adg_canvas_get_left_padding(AdgCanvas *canvas) { AdgCanvasPrivate *data; g_return_val_if_fail(ADG_IS_CANVAS(canvas), 0.); data = adg_canvas_get_instance_private(canvas); return data->left_padding; } /** * adg_canvas_set_paddings: * @canvas: an #AdgCanvas * @top: top padding, in global space * @right: right padding, in global space * @bottom: bottom padding, in global space * @left: left padding, in global space * * Convenient function to set all the paddings at once. * * Since: 1.0 **/ void adg_canvas_set_paddings(AdgCanvas *canvas, gdouble top, gdouble right, gdouble bottom, gdouble left) { g_return_if_fail(ADG_IS_CANVAS(canvas)); g_object_set(canvas, "top-padding", top, "right-padding", right, "bottom-padding", bottom, "left-padding", left, NULL); } /** * adg_canvas_get_paddings: * @canvas: an #AdgCanvas * @top: (out) (nullable): where to store the top padding or NULL * @right: (out) (nullable): where to store the right padding or NULL * @bottom: (out) (nullable): where to store the bottom padding or NULL * @left: (out) (nullable): where to store the left padding or NULL * * Convenient function to get all the paddings at once. * * Since: 1.0 **/ void adg_canvas_get_paddings(AdgCanvas *canvas, gdouble *top, gdouble *right, gdouble *bottom, gdouble *left) { AdgCanvasPrivate *data; g_return_if_fail(ADG_IS_CANVAS(canvas)); data = adg_canvas_get_instance_private(canvas); if (top != NULL) { *top = data->top_padding; } if (right != NULL) { *right = data->right_padding; } if (bottom != NULL) { *bottom = data->bottom_padding; } if (left != NULL) { *left = data->left_padding; } } static void _adg_global_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->title_block) adg_entity_global_changed((AdgEntity *) data->title_block); } static void _adg_local_changed(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); AdgTitleBlock *title_block = data->title_block; if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (title_block != NULL) { const gchar *scale = adg_title_block_get_scale(title_block); if (scale != NULL && scale[0] != '\0') { const cairo_matrix_t *map = adg_entity_get_local_map(entity); gdouble factor = adg_scale_factor(scale); if (map->xx != factor || map->yy != factor) adg_title_block_set_scale(title_block, "---"); } adg_entity_local_changed((AdgEntity *) title_block); } } static void _adg_invalidate(AdgEntity *entity) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); if (data->title_block) adg_entity_invalidate((AdgEntity *) data->title_block); } static void _adg_arrange(AdgEntity *entity) { AdgCanvas *canvas; AdgCanvasPrivate *data; CpmlExtents extents; if (_ADG_OLD_ENTITY_CLASS->arrange) _ADG_OLD_ENTITY_CLASS->arrange(entity); cpml_extents_copy(&extents, adg_entity_get_extents(entity)); /* The extents should be defined, otherwise there is no drawing */ g_return_if_fail(extents.is_defined); canvas = (AdgCanvas *) entity; data = adg_canvas_get_instance_private(canvas); _adg_apply_paddings(canvas, &extents); if (data->size.x > 0 || data->size.y > 0) { const cairo_matrix_t *global = adg_entity_get_global_matrix(entity); CpmlExtents paper; paper.org.x = 0; paper.org.y = 0; paper.size.x = data->size.x; paper.size.y = data->size.y; cairo_matrix_transform_point(global, &paper.org.x, &paper.org.y); cairo_matrix_transform_distance(global, &paper.size.x, &paper.size.y); if (data->size.x > 0) { extents.org.x = paper.org.x; extents.size.x = paper.size.x; } if (data->size.y > 0) { extents.org.y = paper.org.y; extents.size.y = paper.size.y; } } if (data->title_block) { AdgEntity *title_block_entity; const CpmlExtents *title_block_extents; CpmlPair shift; title_block_entity = (AdgEntity *) data->title_block; adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); shift.x = extents.org.x + extents.size.x - title_block_extents->org.x - title_block_extents->size.x; shift.y = extents.org.y + extents.size.y - title_block_extents->org.y - title_block_extents->size.y; /* The following block could be optimized by skipping tiny shift, * usually left by rounding errors */ if (shift.x != 0 || shift.y != 0) { cairo_matrix_t unglobal, map; adg_matrix_copy(&unglobal, adg_entity_get_global_matrix(entity)); cairo_matrix_invert(&unglobal); cairo_matrix_transform_distance(&unglobal, &shift.x, &shift.y); cairo_matrix_init_translate(&map, shift.x, shift.y); adg_entity_transform_global_map(title_block_entity, &map, ADG_TRANSFORM_AFTER); adg_entity_global_changed(title_block_entity); adg_entity_arrange(title_block_entity); title_block_extents = adg_entity_get_extents(title_block_entity); cpml_extents_add(&extents, title_block_extents); } } /* Impose the new extents */ adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgCanvasPrivate *data = adg_canvas_get_instance_private((AdgCanvas *) entity); const CpmlExtents *extents = adg_entity_get_extents(entity); cairo_save(cr); /* Background fill */ cairo_rectangle(cr, extents->org.x - data->left_margin, extents->org.y - data->top_margin, extents->size.x + data->left_margin + data->right_margin, extents->size.y + data->top_margin + data->bottom_margin); adg_entity_apply_dress(entity, data->background_dress, cr); cairo_fill(cr); /* Frame line */ if (data->has_frame) { cairo_rectangle(cr, extents->org.x, extents->org.y, extents->size.x, extents->size.y); cairo_transform(cr, adg_entity_get_global_matrix(entity)); adg_entity_apply_dress(entity, data->frame_dress, cr); cairo_stroke(cr); } cairo_restore(cr); if (data->title_block) adg_entity_render((AdgEntity *) data->title_block, cr); if (_ADG_OLD_ENTITY_CLASS->render) _ADG_OLD_ENTITY_CLASS->render(entity, cr); } static void _adg_apply_paddings(AdgCanvas *canvas, CpmlExtents *extents) { AdgCanvasPrivate *data = adg_canvas_get_instance_private(canvas); extents->org.x -= data->left_padding; extents->size.x += data->left_padding + data->right_padding; extents->org.y -= data->top_padding; extents->size.y += data->top_padding + data->bottom_padding; } /** * adg_canvas_export: * @canvas: an #AdgCanvas * @type: (type gint): the export format * @file: the name of the resulting file * @gerror: return location for errors * * A helper function that provides a bare export functionality. * It basically exports the drawing in @canvas in the @file * in the @type format. Any error will be reported in @gerror, * if not NULL. * * Returns: %TRUE on success, %FALSE on errors. * * Since: 1.0 **/ gboolean adg_canvas_export(AdgCanvas *canvas, cairo_surface_type_t type, const gchar *file, GError **gerror) { AdgEntity *entity; const CpmlExtents *extents; gdouble top, bottom, left, right, width, height, factor; cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); g_return_val_if_fail(file != NULL, FALSE); g_return_val_if_fail(gerror == NULL || *gerror == NULL, FALSE); entity = (AdgEntity *) canvas; adg_entity_arrange(entity); extents = adg_entity_get_extents(entity); factor = adg_canvas_get_factor(canvas); top = factor * adg_canvas_get_top_margin(canvas); bottom = factor * adg_canvas_get_bottom_margin(canvas); left = factor * adg_canvas_get_left_margin(canvas); right = factor * adg_canvas_get_right_margin(canvas); width = factor * extents->size.x + left + right; height = factor * extents->size.y + top + bottom; switch (type) { #ifdef CAIRO_HAS_PNG_FUNCTIONS case CAIRO_SURFACE_TYPE_IMAGE: surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); break; #endif #ifdef CAIRO_HAS_PDF_SURFACE case CAIRO_SURFACE_TYPE_PDF: surface = cairo_pdf_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_PS_SURFACE case CAIRO_SURFACE_TYPE_PS: surface = cairo_ps_surface_create(file, width, height); break; #endif #ifdef CAIRO_HAS_SVG_SURFACE case CAIRO_SURFACE_TYPE_SVG: surface = cairo_svg_surface_create(file, width, height); break; #endif default: surface = NULL; break; } if (surface == NULL) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_SURFACE, "unable to handle surface type '%d'", type); return FALSE; } cairo_surface_set_device_offset(surface, left, top); cairo_surface_set_device_scale(surface, factor, factor); cr = cairo_create(surface); cairo_surface_destroy(surface); adg_entity_render(ADG_ENTITY(canvas), cr); if (cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE) { status = cairo_surface_write_to_png(surface, file); } else { cairo_show_page(cr); status = cairo_status(cr); } cairo_destroy(cr); if (status != CAIRO_STATUS_SUCCESS) { g_set_error(gerror, ADG_CANVAS_ERROR, ADG_CANVAS_ERROR_CAIRO, "cairo reported '%s'", cairo_status_to_string(status)); return FALSE; } return TRUE; } /** * adg_canvas_export_data: * @canvas: an #AdgCanvas * @type: (type gint): the export format * @contents: (out) (array length=length) (element-type guint8): location to store the contents, use g_free() to free * @length: (optional): location to store the length in bytes, or %NULL * @gerror: return location for errors, or %NULL * * A wrapper around adg_canvas_export() that returns the rendered * data instead of writing it to a file. * * Returns: %TRUE on success, %FALSE on errors. * * Since: 1.0 **/ gboolean adg_canvas_export_data(AdgCanvas *canvas, cairo_surface_type_t type, gchar **contents, gsize *length, GError **gerror) { gchar *file; gboolean done; gint h; g_return_val_if_fail(ADG_IS_CANVAS(canvas), FALSE); g_return_val_if_fail(contents != NULL, FALSE); g_return_val_if_fail(gerror == NULL || *gerror == NULL, FALSE); h = g_file_open_tmp(NULL, &file, gerror); if (h < 0) { return FALSE; } done = adg_canvas_export(canvas, type, file, gerror) && g_file_get_contents(file, contents, length, gerror); g_free(file); close(h); return done; } #if GTK3_ENABLED || GTK2_ENABLED #include static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { GtkPageSetup *page_setup; gdouble old_margin; old_margin = *margin; *margin = new_margin; page_setup = adg_canvas_get_page_setup(canvas); if (page_setup == NULL) return; *side += old_margin - new_margin; } /** * adg_canvas_set_paper: * @canvas: an #AdgCanvas * @paper_name: a paper name * @orientation: the page orientation * * A convenient function to set size and margins of @canvas * using a @paper_name and an @orientation value. This should * be a PWG 5101.1-2002 paper name and it will be passed as * is to gtk_paper_size_new(), so use any valid name accepted * by that function. * * This has the same effect as creating a #GtkPageSetup object * and binding it to @canvas with adg_canvas_set_page_setup(): * check its documentation for knowing the implications. * * To reset the size to its default behavior (i.e. autocalculate * it from the entities) just call adg_canvas_set_size_explicit() * passing 0, 0. * * If you want to use your own margins on a named paper size, * set them after the call to this function. * * Since: 1.0 **/ void adg_canvas_set_paper(AdgCanvas *canvas, const gchar *paper_name, GtkPageOrientation orientation) { GtkPageSetup *page_setup; GtkPaperSize *paper_size; g_return_if_fail(ADG_IS_CANVAS(canvas)); g_return_if_fail(paper_name != NULL); page_setup = gtk_page_setup_new(); paper_size = gtk_paper_size_new(paper_name); gtk_page_setup_set_paper_size(page_setup, paper_size); gtk_page_setup_set_orientation(page_setup, orientation); gtk_paper_size_free(paper_size); adg_canvas_set_page_setup(canvas, page_setup); g_object_unref(page_setup); } /** * adg_canvas_set_page_setup: * @canvas: an #AdgCanvas * @page_setup: (allow-none) (transfer none): the page setup * * A convenient function to setup the page of @canvas so it can * also be subsequentially used for printing. It is allowed to * pass NULL as @page_setup to restore the * default page setup. * * When a canvas is bound to a page setup, the paper size is kept * constant. This implies increasing or decreasing the margins * respectively decreases or increases the page size of the * relevant dimension, e.g. increasing the right margin decreases * the width (the x component of the size). * * A reference to @page_setup is added, so there is no need to keep * alive this object after a call to this function. @page_setup can * be retrieved at any time with adg_canvas_get_page_setup(). * * The size and margins provided by @page_setup are immediately * used to set size and margins of @canvas. This means using * NULL as @page_setup just releases the * reference to the previous @page_setup object... all the page * settings are still retained. * * If you want to use your own margins on a page setup, * set them on canvas after the call to this * function or set them on @page_setup before. * * * // By default, canvas does not have an explicit size * adg_canvas_set_page_setup(canvas, a4); * g_object_unref(a4); * // Now canvas has size and margins specified by a4 * // (and a4 should be a prefilled GtkPageSetup object). * adg_canvas_set_page_setup(canvas, NULL); * // Now canvas is no more bound to a4 and that object (if * // not referenced anywhere else) can be garbage-collected. * // The page setup of canvas has not changed though. * adg_canvas_set_size_explicit(canvas, 0, 0); * // Now the page size of canvas has been restored to * // their default behavior. * * * Since: 1.0 **/ void adg_canvas_set_page_setup(AdgCanvas *canvas, GtkPageSetup *page_setup) { gdouble top, right, bottom, left; CpmlPair size; g_return_if_fail(ADG_IS_CANVAS(canvas)); if (page_setup == NULL) { /* By convention, NULL resets the default page but * does not change any other property */ g_object_set_data((GObject *) canvas, "_adg_page_setup", NULL); return; } g_return_if_fail(GTK_IS_PAGE_SETUP(page_setup)); top = gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_POINTS); right = gtk_page_setup_get_right_margin(page_setup, GTK_UNIT_POINTS); bottom = gtk_page_setup_get_bottom_margin(page_setup, GTK_UNIT_POINTS); left = gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_POINTS); size.x = gtk_page_setup_get_page_width(page_setup, GTK_UNIT_POINTS); size.y = gtk_page_setup_get_page_height(page_setup, GTK_UNIT_POINTS); adg_canvas_set_size(canvas, &size); adg_canvas_set_margins(canvas, top, right, bottom, left); g_object_ref(page_setup); g_object_set_data_full((GObject *) canvas, "_adg_page_setup", page_setup, g_object_unref); } /** * adg_canvas_get_page_setup: * @canvas: an #AdgCanvas * * If adg_canvas_set_page_setup() is called, a #GtkPageSetup object * is created and bound to @canvas. This metho returns a pointer * to that internal object or NULL if * adg_canvas_set_page_setup() has not been called before. * * Returns: (allow-none) (transfer none): the #GtkPageSetup with size and margins of @canvas of NULL on no setup found or errors. * * Since: 1.0 **/ GtkPageSetup * adg_canvas_get_page_setup(AdgCanvas *canvas) { g_return_val_if_fail(ADG_IS_CANVAS(canvas), NULL); return g_object_get_data((GObject *) canvas, "_adg_page_setup"); } #else static void _adg_update_margin(AdgCanvas *canvas, gdouble *margin, gdouble *side, gdouble new_margin) { *margin = new_margin; } #endif diff --git a/src/adg/adg-dim-style.c b/src/adg/adg-dim-style.c index 1c3c3e73..f920e1a9 100644 --- a/src/adg/adg-dim-style.c +++ b/src/adg/adg-dim-style.c @@ -1,1495 +1,1495 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dim-style * @short_description: Dimension style related stuff * * Contains parameters on how to build dimensions such as the format of the * label, the different font styles (for value and limits), line style, * offsets of the various dimension components etc. * * A typical use case is to set the quote of angular dimensions in sexagesimal * units. To do this, use something similar to the following code: * * |[ * AdgDimStyle *dim_style = ADG_DIM_STYLE(adg_style_clone(adg_dress_get_fallback(adg_dress_dimension))); * * // Set the decimals and rounding properties * adg_dim_style_set_decimals(dim_style, 0); * adg_dim_style_set_rounding(dim_style, 3); * * // Set the arguments and the format of the quote number * adg_dim_style_set_number_arguments(dim_style, "Dm"); * adg_dim_style_set_number_format(dim_style, "%g°(%g')"); * ]| * * After that you can apply the new style to the * ADG_DRESS_DIMENSION of the entity you want to change, * e.g.: * * |[ * adg_entity_set_style(my_angular_dimension, * ADG_DRESS_DIMENSION, * ADG_STYLE(dim_style)); * ]| * * Since: 1.0 */ /** * AdgDimStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-font-style.h" #include "adg-dash.h" #include "adg-line-style.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-dim-style.h" #include "adg-dim-style-private.h" #include #include #define VALID_FORMATS "aieDdMmSs" G_DEFINE_TYPE_WITH_PRIVATE(AdgDimStyle, adg_dim_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_MARKER1, PROP_MARKER2, PROP_COLOR_DRESS, PROP_VALUE_DRESS, PROP_MIN_DRESS, PROP_MAX_DRESS, PROP_LINE_DRESS, PROP_FROM_OFFSET, PROP_TO_OFFSET, PROP_BEYOND, PROP_BASELINE_SPACING, PROP_LIMITS_SPACING, PROP_QUOTE_SHIFT, PROP_LIMITS_SHIFT, PROP_NUMBER_FORMAT, PROP_NUMBER_ARGUMENTS, PROP_NUMBER_TAG, PROP_DECIMALS, PROP_ROUNDING, }; static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static AdgStyle * _adg_clone (AdgStyle *style); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static AdgMarker * _adg_marker_new (const AdgMarkerData *marker_data); static void _adg_marker_data_set (AdgMarkerData *marker_data, AdgMarker *marker); static void _adg_marker_data_unset (AdgMarkerData *marker_data); static void adg_dim_style_class_init(AdgDimStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->clone = _adg_clone; style_class->apply = _adg_apply; param = g_param_spec_object("marker1", P_("First Marker"), P_("The template entity to use as first marker"), ADG_TYPE_MARKER, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_MARKER1, param); param = g_param_spec_object("marker2", P_("Second Marker"), P_("The template entity to use as second marker"), ADG_TYPE_MARKER, G_PARAM_WRITABLE); g_object_class_install_property(gobject_class, PROP_MARKER2, param); param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("Color dress for the whole dimension"), ADG_DRESS_COLOR_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = adg_param_spec_dress("value-dress", P_("Value Dress"), P_("Font dress for the nominal value of the dimension"), ADG_DRESS_FONT_QUOTE_TEXT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_VALUE_DRESS, param); param = adg_param_spec_dress("min-dress", P_("Minimum Limit Dress"), P_("Font dress for the lower limit value"), ADG_DRESS_FONT_QUOTE_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MIN_DRESS, param); param = adg_param_spec_dress("max-dress", P_("Maximum Limit Dress"), P_("Font dress for the upper limit value"), ADG_DRESS_FONT_QUOTE_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MAX_DRESS, param); param = adg_param_spec_dress("line-dress", P_("Line Dress"), P_("Line dress for the baseline and the extension lines"), ADG_DRESS_LINE_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LINE_DRESS, param); param = g_param_spec_double("from-offset", P_("From Offset"), P_("Offset (in global space) of the extension lines from the path to the quote"), 0, G_MAXDOUBLE, 5, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FROM_OFFSET, param); param = g_param_spec_double("to-offset", P_("To Offset"), P_("How many extend (in global space) the extension lines after hitting the baseline"), 0, G_MAXDOUBLE, 5, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TO_OFFSET, param); param = g_param_spec_double("beyond", P_("Beyond Length"), P_("How much the baseline should be extended (in global space) beyond the extension lines on dimensions with outside markers"), 0, G_MAXDOUBLE, 20, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BEYOND, param); param = g_param_spec_double("baseline-spacing", P_("Baseline Spacing"), P_("Distance between two consecutive baselines while stacking dimensions"), 0, G_MAXDOUBLE, 32, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_BASELINE_SPACING, param); param = g_param_spec_double("limits-spacing", P_("Limits Spacing"), P_("Distance between limits/tolerances"), -G_MAXDOUBLE, G_MAXDOUBLE, 2, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LIMITS_SPACING, param); param = g_param_spec_boxed("quote-shift", P_("Quote Shift"), P_("Used to specify a smooth displacement (in global space) of the quote by taking as reference the perfect compact position (the middle of the baseline on common linear dimension, for instance)"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_QUOTE_SHIFT, param); param = g_param_spec_boxed("limits-shift", P_("Limits Shift"), P_("Used to specify a smooth displacement (in global space) for the limits/tolerances by taking as reference the perfect compact position"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LIMITS_SHIFT, param); param = g_param_spec_string("number-format", P_("Number Format"), P_("The format (in printf style) of the numeric component of the basic value"), "%-.7g", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_FORMAT, param); param = g_param_spec_string("number-arguments", P_("Number Arguments"), P_("The arguments to pass to the format function: see adg_dim_style_set_number_arguments() for further details"), "d", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_ARGUMENTS, param); param = g_param_spec_string("number-tag", P_("Number Tag"), P_("The tag to substitute inside the value template string"), "<>", G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_NUMBER_TAG, param); param = g_param_spec_int("decimals", P_("Rounded Value Decimals"), P_("Number of significant decimals to use in the format string for rounded values (-1 to disable)"), -1, G_MAXINT, 2, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DECIMALS, param); param = g_param_spec_int("rounding", P_("Raw Value Decimals"), P_("Number of significant decimals the raw value must be rounded to (-1 to disable)"), -1, G_MAXINT, 6, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ROUNDING, param); } static void adg_dim_style_init(AdgDimStyle *dim_style) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private(dim_style); data->marker1.type = 0; data->marker1.n_properties = 0; data->marker1.names = NULL; data->marker1.values = NULL; data->marker2.type = 0; data->marker2.n_properties = 0; data->marker2.names = NULL; data->marker2.values = NULL; data->color_dress = ADG_DRESS_COLOR_DIMENSION; data->value_dress = ADG_DRESS_FONT_QUOTE_TEXT; data->min_dress = ADG_DRESS_FONT_QUOTE_ANNOTATION; data->max_dress = ADG_DRESS_FONT_QUOTE_ANNOTATION; data->line_dress = ADG_DRESS_LINE_DIMENSION; data->marker_dress = ADG_DRESS_UNDEFINED; data->from_offset = 6; data->to_offset = 6; data->beyond = 20; data->baseline_spacing = 32; data->limits_spacing = 0; data->quote_shift.x = 4; data->quote_shift.y = -1; data->limits_shift.x = +2; data->limits_shift.y = +2; data->number_format = g_strdup("%-.7g"); data->number_arguments = g_strdup("d"); data->number_tag = g_strdup("<>"); data->decimals = 2; data->rounding = 6; } static void _adg_finalize(GObject *object) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) object); _adg_marker_data_unset(&data->marker1); _adg_marker_data_unset(&data->marker2); g_free(data->number_format); data->number_format = NULL; g_free(data->number_arguments); data->number_arguments = NULL; g_free(data->number_tag); data->number_tag = NULL; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) object); switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_VALUE_DRESS: g_value_set_enum(value, data->value_dress); break; case PROP_MIN_DRESS: g_value_set_enum(value, data->min_dress); break; case PROP_MAX_DRESS: g_value_set_enum(value, data->max_dress); break; case PROP_LINE_DRESS: g_value_set_enum(value, data->line_dress); break; case PROP_FROM_OFFSET: g_value_set_double(value, data->from_offset); break; case PROP_TO_OFFSET: g_value_set_double(value, data->to_offset); break; case PROP_BEYOND: g_value_set_double(value, data->beyond); break; case PROP_BASELINE_SPACING: g_value_set_double(value, data->baseline_spacing); break; case PROP_LIMITS_SPACING: g_value_set_double(value, data->limits_spacing); break; case PROP_QUOTE_SHIFT: g_value_set_boxed(value, &data->quote_shift); break; case PROP_LIMITS_SHIFT: g_value_set_boxed(value, &data->limits_shift); break; case PROP_NUMBER_FORMAT: g_value_set_string(value, data->number_format); break; case PROP_NUMBER_ARGUMENTS: g_value_set_string(value, data->number_arguments); break; case PROP_NUMBER_TAG: g_value_set_string(value, data->number_tag); break; case PROP_DECIMALS: g_value_set_int(value, data->decimals); break; case PROP_ROUNDING: g_value_set_int(value, data->rounding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgDimStyle *dim_style = (AdgDimStyle *) object; AdgDimStylePrivate *data = adg_dim_style_get_instance_private(dim_style); switch (prop_id) { case PROP_MARKER1: _adg_marker_data_set(&data->marker1, g_value_get_object(value)); break; case PROP_MARKER2: _adg_marker_data_set(&data->marker2, g_value_get_object(value)); break; case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_VALUE_DRESS: data->value_dress = g_value_get_enum(value); break; case PROP_MIN_DRESS: data->min_dress = g_value_get_enum(value); break; case PROP_MAX_DRESS: data->max_dress = g_value_get_enum(value); break; case PROP_LINE_DRESS: data->line_dress = g_value_get_enum(value); break; case PROP_FROM_OFFSET: data->from_offset = g_value_get_double(value); break; case PROP_TO_OFFSET: data->to_offset = g_value_get_double(value); break; case PROP_BEYOND: data->beyond = g_value_get_double(value); break; case PROP_BASELINE_SPACING: data->baseline_spacing = g_value_get_double(value); break; case PROP_LIMITS_SPACING: data->limits_spacing = g_value_get_double(value); break; case PROP_QUOTE_SHIFT: cpml_pair_copy(&data->quote_shift, g_value_get_boxed(value)); break; case PROP_LIMITS_SHIFT: cpml_pair_copy(&data->limits_shift, g_value_get_boxed(value)); break; case PROP_NUMBER_FORMAT: g_free(data->number_format); data->number_format = g_value_dup_string(value); break; case PROP_NUMBER_ARGUMENTS: { const gchar *arguments = g_value_get_string(value); g_return_if_fail(arguments == NULL || strspn(arguments, VALID_FORMATS) == strlen(arguments)); g_free(data->number_arguments); data->number_arguments = g_strdup(arguments); break; } case PROP_NUMBER_TAG: g_free(data->number_tag); data->number_tag = g_value_dup_string(value); break; case PROP_DECIMALS: data->decimals = g_value_get_int(value); break; case PROP_ROUNDING: data->rounding = g_value_get_int(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_dim_style_new: * * Constructs a new empty dimension style initialized with default params. * * Returns: (transfer full): a newly created dimension style. * * Since: 1.0 **/ AdgDimStyle * adg_dim_style_new(void) { return g_object_new(ADG_TYPE_DIM_STYLE, NULL); } /** * adg_dim_style_set_marker1: * @dim_style: an #AdgStyle * @marker: an #AdgMarker derived entity * * Uses @marker as entity template to generate a new marker entity * when a call to adg_dim_style_marker1_new() is made. It is allowed * to pass NULL as @marker, in which case the * template data of the first marker are unset. * * This method duplicates internally the property values of @marker, * so any further change to @marker does not affect @dim_style anymore. * This also means @marker could be destroyed without problems after * this call because @dim_style uses only its property values and does * not add any references to @marker. * * Since: 1.0 **/ void adg_dim_style_set_marker1(AdgDimStyle *dim_style, AdgMarker *marker) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "marker1", marker, NULL); } /** * adg_dim_style_marker1_new: * @dim_style: an #AdgDimStyle * * Creates a new marker entity by cloning the #AdgDimStyle:marker1 * object. The returned entity should be unreferenced with * g_object_unref() when no longer needed. * * Returns: (transfer full): a newly created marker or NULL if the #AdgDimStyle:marker1 property is not set or on errors. * * Since: 1.0 **/ AdgMarker * adg_dim_style_marker1_new(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return _adg_marker_new(&data->marker1); } /** * adg_dim_style_set_marker2: * @dim_style: an #AdgStyle * @marker: an #AdgMarker derived entity * * Uses @marker as entity template to generate a new marker entity * when a call to adg_dim_style_marker2_new() is made. It is allowed * to pass NULL as @marker, in which case the * template data of the second marker are unset. * * This method duplicates internally the property values of @marker, * so any further change to @marker does not affect @dim_style anymore. * This also means @marker could be destroyed without problems after * this call because @dim_style uses only its property values and does * not add any references to @marker. * * Since: 1.0 **/ void adg_dim_style_set_marker2(AdgDimStyle *dim_style, AdgMarker *marker) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "marker2", marker, NULL); } /** * adg_dim_style_marker2_new: * @dim_style: an #AdgDimStyle * * Creates a new marker entity by cloning the #AdgDimStyle:marker2 * object. The returned entity should be unreferenced with * g_object_unref() when no longer needed. * * Returns: (transfer full): a newly created marker or NULL if the #AdgDimStyle:marker2 property is not set or on errors. * * Since: 1.0 **/ AdgMarker * adg_dim_style_marker2_new(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return _adg_marker_new(&data->marker2); } /** * adg_dim_style_set_color_dress: * @dim_style: an #AdgDimStyle object * @dress: the new color dress * * Sets a new color dress on @dim_style. * * Since: 1.0 **/ void adg_dim_style_set_color_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "color-dress", dress, NULL); } /** * adg_dim_style_get_color_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style color dress to be used. This dress should be * intended as a fallback color as it could be overriden by more - * specific dresses, such as a color explicitely specified on the + * specific dresses, such as a color explicitly specified on the * #AdgDimStyle:value-dress. * * Returns: (transfer none): the color dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_color_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->color_dress; } /** * adg_dim_style_set_value_dress: * @dim_style: an #AdgDimStyle object * @dress: the new basic value font style * * Sets a new dress on @dim_style for the basic value. * * Since: 1.0 **/ void adg_dim_style_set_value_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "value-dress", dress, NULL); } /** * adg_dim_style_get_value_dress: * @dim_style: an #AdgDimStyle object * * Gets the font dress to be used for the basic value of dimensions * with @dim_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_value_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->value_dress; } /** * adg_dim_style_set_min_dress: * @dim_style: an #AdgDimStyle object * @dress: the new lower limit dress * * Sets a new dress on @dim_style for the lower limit value. * * Since: 1.0 **/ void adg_dim_style_set_min_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "min-dress", dress, NULL); } /** * adg_dim_style_get_min_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style dress to be used for the lower limit. * * Returns: (transfer none): the lower limit dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_min_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->min_dress; } /** * adg_dim_style_set_max_dress: * @dim_style: an #AdgDimStyle object * @dress: the new upper limit dress * * Sets a new dress on @dim_style for the upper limit value. * * Since: 1.0 **/ void adg_dim_style_set_max_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "max-dress", dress, NULL); } /** * adg_dim_style_get_max_dress: * @dim_style: an #AdgDimStyle object * * Gets the @dim_style dress to be used for the upper limit. * * Returns: (transfer none): the upper limit dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_max_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->max_dress; } /** * adg_dim_style_set_line_dress: * @dim_style: an #AdgDimStyle object * @dress: the new line dress * * Sets a new line dress on @dim_style. * * Since: 1.0 **/ void adg_dim_style_set_line_dress(AdgDimStyle *dim_style, AdgDress dress) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "line-dress", dress, NULL); } /** * adg_dim_style_get_line_dress: * @dim_style: an #AdgDimStyle object * * Gets the line dress to be used for rendering the base and * the extension lines with @dim_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_dim_style_get_line_dress(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), ADG_DRESS_UNDEFINED); data = adg_dim_style_get_instance_private(dim_style); return data->line_dress; } /** * adg_dim_style_set_from_offset: * @dim_style: an #AdgDimStyle object * @offset: the new offset * * Sets a new value in the #AdgDimStyle:from-offset property. * * Since: 1.0 **/ void adg_dim_style_set_from_offset(AdgDimStyle *dim_style, gdouble offset) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "from-offset", offset, NULL); } /** * adg_dim_style_get_from_offset: * @dim_style: an #AdgDimStyle object * * Gets the distance (in global space) the extension lines must keep from the * sensed points. * * Returns: the requested distance. * * Since: 1.0 **/ gdouble adg_dim_style_get_from_offset(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->from_offset; } /** * adg_dim_style_set_to_offset: * @dim_style: an #AdgDimStyle object * @offset: the new offset * * Sets a new value in the #AdgDimStyle:to-offset property. * * Since: 1.0 **/ void adg_dim_style_set_to_offset(AdgDimStyle *dim_style, gdouble offset) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "to-offset", offset, NULL); } /** * adg_dim_style_get_to_offset: * @dim_style: an #AdgDimStyle object * * Gets how much (in global space) the extension lines must extend after * crossing the baseline. * * Returns: the requested distance. * * Since: 1.0 **/ gdouble adg_dim_style_get_to_offset(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->to_offset; } /** * adg_dim_style_set_beyond: * @dim_style: an #AdgDimStyle object * @beyond: the new length * * Sets a new value in the #AdgDimStyle:beyond property. * * Since: 1.0 **/ void adg_dim_style_set_beyond(AdgDimStyle *dim_style, gdouble beyond) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "beyond", beyond, NULL); } /** * adg_dim_style_get_beyond: * @dim_style: an #AdgDimStyle object * * Gets how much (in global space) the baseline should extend beyond * the extension lines on dimension with outside markers. * * Returns: the requested beyond length. * * Since: 1.0 **/ gdouble adg_dim_style_get_beyond(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->beyond; } /** * adg_dim_style_set_baseline_spacing: * @dim_style: an #AdgDimStyle object * @spacing: the new spacing * * Sets a new value in the #AdgDimStyle:baseline-spacing value. * * Since: 1.0 **/ void adg_dim_style_set_baseline_spacing(AdgDimStyle *dim_style, gdouble spacing) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "baseline-spacing", spacing, NULL); } /** * adg_dim_style_get_baseline_spacing: * @dim_style: an #AdgDimStyle object * * Gets the distance between two consecutive baselines * while stacking dimensions. * * Returns: the requested spacing. * * Since: 1.0 **/ gdouble adg_dim_style_get_baseline_spacing(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->baseline_spacing; } /** * adg_dim_style_set_limits_spacing: * @dim_style: an #AdgDimStyle object * @spacing: the new spacing * * Sets a new #AdgDimStyle:limits-spacing value. * * Since: 1.0 **/ void adg_dim_style_set_limits_spacing(AdgDimStyle *dim_style, gdouble spacing) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "limits-spacing", spacing, NULL); } /** * adg_dim_style_get_limits_spacing: * @dim_style: an #AdgDimStyle object * * Gets the distance (in global space) between the limits/tolerances. * * Returns: the requested spacing. * * Since: 1.0 **/ gdouble adg_dim_style_get_limits_spacing(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), 0); data = adg_dim_style_get_instance_private(dim_style); return data->limits_spacing; } /** * adg_dim_style_set_quote_shift: * @dim_style: an #AdgDimStyle object * @shift: the new displacement * * Sets a new #AdgDimStyle:quote-shift value. * * Since: 1.0 **/ void adg_dim_style_set_quote_shift(AdgDimStyle *dim_style, const CpmlPair *shift) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "quote-shift", shift, NULL); } /** * adg_dim_style_get_quote_shift: * @dim_style: an #AdgDimStyle object * * Gets the smooth displacement of the quote. The returned pointer refers * to an internal allocated struct and must not be modified or freed. * * Returns: (transfer none): the requested shift. * * Since: 1.0 **/ const CpmlPair * adg_dim_style_get_quote_shift(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return &data->quote_shift; } /** * adg_dim_style_set_limits_shift: * @dim_style: an #AdgDimStyle object * @shift: the new displacement * * Sets a new #AdgDimStyle:limits-shift value. * * Since: 1.0 **/ void adg_dim_style_set_limits_shift(AdgDimStyle *dim_style, const CpmlPair *shift) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "limits-shift", shift, NULL); } /** * adg_dim_style_get_limits_shift: * @dim_style: an #AdgDimStyle object * * Gets the smooth displacement for the limits. The returned pointer * refers to an internal allocated struct and must not be modified or freed. * * Returns: (transfer none): the requested shift. * * Since: 1.0 **/ const CpmlPair * adg_dim_style_get_limits_shift(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return &data->limits_shift; } /** * adg_dim_style_set_number_format: * @dim_style: an #AdgDimStyle object * @format: the new format to adopt * * Sets a new value in the #AdgDimStyle:number-format property. * * @format is similar to a printf() style format string but only 'e', 'E', * 'f', 'F', 'g' and 'G' specifiers are allowed. For reference, the * implementation leverages the g_ascii_formatd() GLib function. If you want * to use special characters (%"(", %")" and %"%") as literals you need to * escape them with a backslash. * * You can use round parenthesis to group together one or more % directives * with other related literals. In that case, when the value bound to the * directive will be 0, the whole group will disappear. Some example: * * |[ * AdgDim *dim; * AdgDimStyle *dim_style; * gchar *text; * * dim = ADG_DIM(adg_ldim_new()); * dim_style = ADG_DIM_STYLE(adg_entity_style(ADG_ENTITY(dim), ADG_DRESS_DIMENSION)); * * adg_dim_style_set_number_arguments(dim_style, "dD"); * adg_dim_style_set_number_format(dim_style, "(%g)( truncated to %g)"); * * text = adg_dim_get_text(dim, 1.2); * g_print("%s\n", text); // Prints "1.2 truncated to 1" * g_free(text); * * text = adg_dim_get_text(dim, 0.2); * g_print("%s\n", text); // Prints "0.2" * g_free(text); * * text = adg_dim_get_text(dim, 0); * g_print("%s\n", text); // Prints "" * g_free(text); * ]| * * Groups can be nested and can have more than one value, in which case all * the values contained in a group must be 0 in order to let it disappear. * This comes in handy for removing trailing 0 fields. A typical example is * the sexagesimal representation of angles: * * |[ * adg_dim_style_set_number_arguments(dim_style, "DMs"); * adg_dim_style_set_number_format(dim_style, "%g°(%g'(%g\"))"); * * text = adg_dim_get_text(dim, 1.5); * g_print("%s\n", text); // Prints "1°30'" * g_free(text); * * text = adg_dim_get_text(dim, 2.002777); * g_print("%s\n", text); // Prints "2°0'10\"" * g_free(text); * ]| * * Since: 1.0 **/ void adg_dim_style_set_number_format(AdgDimStyle *dim_style, const gchar *format) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-format", format, NULL); } /** * adg_dim_style_get_number_format: * @dim_style: an #AdgDimStyle object * * Gets the number format (in printf style) of this quoting style. The * returned pointer refers to internally managed text that must not be * modified or freed. * * Returns: (transfer none): the requested format. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_format(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_format; } /** * adg_dim_style_set_number_arguments: * @dim_style: an #AdgDimStyle object * @arguments: the arguments to pass to the formatting function * * A string that identifies the arguments to pass to the formatting function. * See adg_dim_style_convert() to know the allowed character that can be * included into this string. * * The number of arguments (i.e. the @arguments length) must match the * #AdgDimStyle:number-format property (i.e. the number of % directives * included in that property). See adg_dim_style_set_number_format() for more * technical details and some examples. * * Since: 1.0 **/ void adg_dim_style_set_number_arguments(AdgDimStyle *dim_style, const gchar *arguments) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-arguments", arguments, NULL); } /** * adg_dim_style_get_number_arguments: * @dim_style: an #AdgDimStyle object * * Gets the arguments used by the formatting function. See * adg_dim_style_set_number_arguments() for details on what this means. * * Returns: (transfer none): the requested arguments. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_arguments(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_arguments; } /** * adg_dim_style_set_number_tag: * @dim_style: an #AdgDimStyle object * @tag: the new tag * * Sets a new tag in the #AdgDimStyle:number-tag property. * * Since: 1.0 **/ void adg_dim_style_set_number_tag(AdgDimStyle *dim_style, const gchar *tag) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "number-tag", tag, NULL); } /** * adg_dim_style_get_number_tag: * @dim_style: an #AdgDimStyle object * * Gets the number tag of @dim_style. This tag will be used while * generating the set values of the dimensions bound to this style: * check the #AdgDim:value documentation for further details. * * The returned pointer refers to internally managed text that * must not be modified or freed. * * Returns: (transfer none): the requested tag. * * Since: 1.0 **/ const gchar * adg_dim_style_get_number_tag(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), NULL); data = adg_dim_style_get_instance_private(dim_style); return data->number_tag; } /** * adg_dim_style_set_decimals: * @dim_style: an #AdgDimStyle object * @decimals: number of significant decimals * * Sets a new value in the #AdgDimStyle:decimals property. * * Since: 1.0 **/ void adg_dim_style_set_decimals(AdgDimStyle *dim_style, gint decimals) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "decimals", decimals, NULL); } /** * adg_dim_style_get_decimals: * @dim_style: an #AdgDimStyle object * * Gets the decimals the value of a dimension will be rounded to before the * rendering. * * Returns: the number of significant decimals or -2 on errors. * * Since: 1.0 **/ gint adg_dim_style_get_decimals(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), -2); data = adg_dim_style_get_instance_private(dim_style); return data->decimals; } /** * adg_dim_style_set_rounding: * @dim_style: an #AdgDimStyle object * @rounding: number of significant decimals of the raw value * * Sets a new value in the #AdgDimStyle:rounding property. * * Since: 1.0 **/ void adg_dim_style_set_rounding(AdgDimStyle *dim_style, gint rounding) { g_return_if_fail(ADG_IS_DIM_STYLE(dim_style)); g_object_set(dim_style, "rounding", rounding, NULL); } /** * adg_dim_style_get_rounding: * @dim_style: an #AdgDimStyle object * * Gets the number of decimals the raw value must be rounded to. * * Returns: the number of significant decimals or -2 on errors. * * Since: 1.0 **/ gint adg_dim_style_get_rounding(AdgDimStyle *dim_style) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), -2); data = adg_dim_style_get_instance_private(dim_style); return data->rounding; } /** * adg_dim_style_convert: * @dim_style: an #AdgDimStyle object * @value: (inout): the value to convert * @format: the convertion to apply * * Converts @value using the specific @format algorithm and store the * result in @value itself. If @value is %NULL, nothing is performed. * * In the following the allowed values for @format: * - 'a': the raw @value, i.e. no conversion is performed; * - 'i': the raw number of minutes, i.e. the fractional part of @value x 60 * - 'e': the raw number of seconds, i.e. the fractional part of the raw * number of minutes x 60 * - 'D': the truncated value of the raw value ('a'); * - 'd': the rounded value of the raw value ('a'); * - 'M': the truncated value of the raw number of minutes ('i'); * - 'm': the rounded value of the raw number of minutes ('i'); * - 'S': the truncated value of the raw number of seconds ('e'); * - 's': the rounded value of the raw number of seconds ('e'); * * The rounding is done according to the number of decimal specified in * #AdgDimStyle:decimals. * * Returns: %TRUE if @value has been converted, %FALSE on errors. * * Since: 1.0 **/ gboolean adg_dim_style_convert(AdgDimStyle *dim_style, gdouble *value, gchar format) { AdgDimStylePrivate *data; g_return_val_if_fail(ADG_IS_DIM_STYLE(dim_style), FALSE); /* Inout parameter not provided: just return FALSE */ if (value == NULL) { return FALSE; } data = adg_dim_style_get_instance_private(dim_style); /* Round the raw value, if requested */ if (data->rounding > -1) { gdouble coefficient = pow(10, data->rounding); *value = round(*value * coefficient) / coefficient; } switch (format) { case 'a': /* Raw value */ break; case 'i': /* Raw minutes */ *value = (*value - (gint) *value) * 60; break; case 'e': /* Raw seconds */ adg_dim_style_convert(dim_style, value, 'i'); *value = (*value - (gint) *value) * 60; break; case 'D': /* Truncated value */ *value = (gint) *value; break; case 'd': /* Rounded value */ *value = adg_round(*value, data->decimals); break; case 'M': /* Truncated minutes */ adg_dim_style_convert(dim_style, value, 'i'); *value = (gint) *value; break; case 'm': /* Rounded minutes */ adg_dim_style_convert(dim_style, value, 'i'); *value = adg_round(*value, data->decimals); break; case 'S': /* Truncated seconds */ adg_dim_style_convert(dim_style, value, 'e'); *value = (gint) *value; break; case 's': /* Rounded seconds */ adg_dim_style_convert(dim_style, value, 'e'); *value = adg_round(*value, data->decimals); break; default: g_return_val_if_reached(FALSE); return FALSE; } return TRUE; } static AdgStyle * _adg_clone(AdgStyle *style) { AdgDimStyle *dim_style; AdgMarker *marker; dim_style = (AdgDimStyle *) adg_object_clone(G_OBJECT(style)); /* Manually clone the marker because the underlying properties are * writable only */ marker = adg_dim_style_marker1_new((AdgDimStyle *) style); adg_dim_style_set_marker1(dim_style, marker); g_object_unref(marker); marker = adg_dim_style_marker2_new((AdgDimStyle *) style); adg_dim_style_set_marker2(dim_style, marker); g_object_unref(marker); return (AdgStyle *) dim_style; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgDimStylePrivate *data = adg_dim_style_get_instance_private((AdgDimStyle *) style); adg_entity_apply_dress(entity, data->color_dress, cr); } static AdgMarker * _adg_marker_new(const AdgMarkerData *marker_data) { if (marker_data->type == 0) return NULL; return (AdgMarker *) g_object_new_with_properties(marker_data->type, marker_data->n_properties, marker_data->names, marker_data->values); } static void _adg_marker_data_set(AdgMarkerData *marker_data, AdgMarker *marker) { g_return_if_fail(marker == NULL || ADG_IS_MARKER(marker)); /* Free the previous marker data, if any */ _adg_marker_data_unset(marker_data); if (marker) { GObject *object; GParamSpec **specs; GParamSpec *spec; guint n; GValue *value; object = (GObject *) marker; specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(marker), &marker_data->n_properties); marker_data->type = G_TYPE_FROM_INSTANCE(marker); marker_data->names = g_new0(const gchar *, marker_data->n_properties); marker_data->values = g_new0(GValue, marker_data->n_properties); for (n = 0; n < marker_data->n_properties; ++n) { spec = specs[n]; /* Using intern strings because `name` is const. * GObject properties are internally managed using non-static * GQuark, so g_intern_string() is the way to go */ marker_data->names[n] = g_intern_string(spec->name); value = &marker_data->values[n]; g_value_init(value, spec->value_type); g_object_get_property(object, spec->name, value); } g_free(specs); } } static void _adg_marker_data_unset(AdgMarkerData *marker_data) { guint n; for (n = 0; n < marker_data->n_properties; ++n) { g_value_unset(&marker_data->values[n]); } g_free(marker_data->names); g_free(marker_data->values); marker_data->type = 0; marker_data->n_properties = 0; marker_data->names = NULL; marker_data->values = NULL; } diff --git a/src/adg/adg-dim.c b/src/adg/adg-dim.c index 66b4b82a..f06340c8 100644 --- a/src/adg/adg-dim.c +++ b/src/adg/adg-dim.c @@ -1,1844 +1,1844 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-dim * @short_description: Root abstract class for all dimension entities * * The #AdgDim class is the base stub of all the dimension entities. * * Since: 1.0 **/ /** * AdgDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgDimClass: * @quote_angle: virtual method that must return the rotation angle of the * quote (in radians) of the current dimension. * @default_value: abstract virtual method that must return the default value * (as a newly allocated string to be freed with g_free()) of * the current dimension. * * The default implementation of @quote_angle flips the quote if it should be * rotated in the bottom right half of the circle, that is: * * * if 1/3 PI <= angle <= -3/4 PI; then angle += PI. * * * The virtual method @default_value instead *must* be implemented by any * derived class. The default implementation will trigger an error if called. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-text-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-point.h" #include "adg-marker.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_dim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_dim_parent_class) /* A convenience macro for ORing two AdgThreeState values */ #define OR_3S(a,b) ( \ ((a) == ADG_THREE_STATE_ON || (b) == ADG_THREE_STATE_ON) ? ADG_THREE_STATE_ON : \ ((a) == ADG_THREE_STATE_UNKNOWN && (b) == ADG_THREE_STATE_UNKNOWN) ? ADG_THREE_STATE_UNKNOWN : \ ADG_THREE_STATE_OFF ) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgDim, adg_dim, ADG_TYPE_ENTITY) enum { PROP_0, PROP_DIM_DRESS, PROP_REF1, PROP_REF2, PROP_POS, PROP_LEVEL, PROP_OUTSIDE, PROP_DETACHED, PROP_VALUE, PROP_MIN, PROP_MAX }; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static gboolean _adg_compute_geometry (AdgDim *dim); static gchar * _adg_default_value (AdgDim *dim); static gdouble _adg_quote_angle (gdouble angle); static gboolean _adg_set_outside (AdgDim *dim, AdgThreeState outside); static gboolean _adg_set_detached (AdgDim *dim, AdgThreeState detached); static gboolean _adg_set_value (AdgDim *dim, const gchar *value); static gboolean _adg_set_min (AdgDim *dim, const gchar *min); static gboolean _adg_set_max (AdgDim *dim, const gchar *max); static gboolean _adg_replace (const GMatchInfo *match_info, GString *result, gpointer user_data); static gchar * _adg_text_expand (AdgDimReplaceData *data); static void adg_dim_class_init(AdgDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; klass->compute_geometry = _adg_compute_geometry; klass->quote_angle = _adg_quote_angle; klass->default_value = _adg_default_value; param = adg_param_spec_dress("dim-dress", P_("Dimension Dress"), P_("The dress to use for rendering this dimension"), ADG_DRESS_DIMENSION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DIM_DRESS, param); param = g_param_spec_boxed("ref1", P_("First Reference"), P_("First reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF1, param); param = g_param_spec_boxed("ref2", P_("Second Reference"), P_("Second reference point of the dimension"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_REF2, param); param = g_param_spec_boxed("pos", P_("Position"), P_("The reference position of the quote: it will be combined with \"level\" to get the real quote position"), ADG_TYPE_POINT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_POS, param); param = g_param_spec_double("level", P_("Level"), P_("The dimension level, that is the factor to multiply the baseline spacing (defined in the dimension style) to get the offset from pos where the quote should be rendered"), -G_MAXDOUBLE, G_MAXDOUBLE, 1.0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LEVEL, param); param = g_param_spec_enum("outside", P_("Outside"), P_("Whether the arrows must be inside the extension lines (ADG_THREE_STATE_OFF), must be extended outside the extension lines (ADG_THREE_STATE_ON) or should be automatically handled depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_OUTSIDE, param); param = g_param_spec_enum("detached", P_("Detached Quote"), P_("Where the quote must be positioned: in the middle of the base line (ADG_THREE_STATE_OFF), near the pos point (ADG_THREE_STATE_ON) or should be automatically deducted depending on the available space"), ADG_TYPE_THREE_STATE, ADG_THREE_STATE_UNKNOWN, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_DETACHED, param); param = g_param_spec_string("value", P_("Value Template"), P_("The template string to be used for generating the set value of the quote"), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_VALUE, param); param = g_param_spec_string("min", P_("Minimum Value or Low Tolerance"), P_("The minimum value allowed or the lowest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MIN, param); param = g_param_spec_string("max", P_("Maximum Value or High Tolerance"), P_("The maximum value allowed or the highest tolerance from value (depending of the dimension style): set to NULL to suppress"), NULL, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_MAX, param); } static void adg_dim_init(AdgDim *dim) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); data->dim_dress = ADG_DRESS_DIMENSION; data->ref1 = NULL; data->ref2 = NULL; data->pos = NULL; data->level = 1; data->outside = ADG_THREE_STATE_UNKNOWN; data->detached = ADG_THREE_STATE_UNKNOWN; data->min = NULL; data->max = NULL; data->geometry.computed = FALSE; data->geometry.notice = NULL; #if 0 /* This one is G_PARAM_CONSTRUCT, so set by property inizialization */ data->value = NULL #endif } static void _adg_dispose(GObject *object) { AdgEntity *entity = (AdgEntity *) object; AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); if (data->quote.entity) { g_object_unref(data->quote.entity); data->quote.entity = NULL; } if (data->ref1) data->ref1 = adg_entity_point(entity, data->ref1, NULL); if (data->ref2) data->ref2 = adg_entity_point(entity, data->ref2, NULL); if (data->pos) data->pos = adg_entity_point(entity, data->pos, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); g_free(data->value); g_free(data->min); g_free(data->max); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) object); switch (prop_id) { case PROP_DIM_DRESS: g_value_set_enum(value, data->dim_dress); break; case PROP_REF1: g_value_set_boxed(value, data->ref1); break; case PROP_REF2: g_value_set_boxed(value, data->ref2); break; case PROP_POS: g_value_set_boxed(value, data->pos); break; case PROP_LEVEL: g_value_set_double(value, data->level); break; case PROP_OUTSIDE: g_value_set_enum(value, data->outside); break; case PROP_DETACHED: g_value_set_enum(value, data->detached); break; case PROP_VALUE: g_value_set_string(value, data->value); break; case PROP_MIN: g_value_set_string(value, data->min); break; case PROP_MAX: g_value_set_string(value, data->max); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgDim *dim = (AdgDim *) object; AdgDimPrivate *data = adg_dim_get_instance_private(dim); switch (prop_id) { case PROP_DIM_DRESS: data->dim_dress = g_value_get_enum(value); break; case PROP_REF1: data->ref1 = adg_entity_point(entity, data->ref1, g_value_get_boxed(value)); break; case PROP_REF2: data->ref2 = adg_entity_point(entity, data->ref2, g_value_get_boxed(value)); break; case PROP_POS: data->pos = adg_entity_point(entity, data->pos, g_value_get_boxed(value)); break; case PROP_LEVEL: data->level = g_value_get_double(value); break; case PROP_OUTSIDE: _adg_set_outside(dim, g_value_get_enum(value)); break; case PROP_DETACHED: _adg_set_detached(dim, g_value_get_enum(value)); break; case PROP_VALUE: _adg_set_value(dim, g_value_get_string(value)); break; case PROP_MIN: _adg_set_min(dim, g_value_get_string(value)); break; case PROP_MAX: _adg_set_max(dim, g_value_get_string(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_dim_get_dim_style: * @dim: an #AdgDim * * Gets the internal cached dim style of @dim. * * Returns: (transfer none): the internal AdgDimStyle style. * * Since: 1.0 **/ AdgDimStyle * adg_dim_get_dim_style(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->dim_style; } /** * adg_dim_set_dim_dress: * @dim: an #AdgDim * @dress: the new #AdgDress to use * * Sets a new dimension dress to @dim. The new dress must be * related to the original dress for this property: you cannot * set a dress used for line styles to a dress managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_dim_set_dim_dress(AdgDim *dim, AdgDress dress) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "dim-dress", dress, NULL); } /** * adg_dim_get_dim_dress: * @dim: an #AdgDim * * Gets the dimension dress to be used in rendering @dim. * * Returns: (transfer none): the current dimension dress. * * Since: 1.0 **/ AdgDress adg_dim_get_dim_dress(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_DRESS_UNDEFINED); data = adg_dim_get_instance_private(dim); return data->dim_dress; } /** * adg_dim_set_ref1: * @dim: an #AdgDim * @ref1: the new point to use as first reference * * Sets the #AdgDim:ref1 property to @ref1. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * @ref1 can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref1(AdgDim *dim, const AdgPoint *ref1) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref1", ref1, NULL); } /** * adg_dim_set_ref1_explicit: * @dim: an #AdgDim * @x: x coordinate of the first reference point * @y: y coordinate of the first reference point * * Sets the #AdgDim:ref1 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref1_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref1_from_pair: * @dim: an #AdgDim * @ref1: the coordinates pair of the first reference point * * Convenient function to set the #AdgDim:ref1 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref1_from_pair(AdgDim *dim, const CpmlPair *ref1) { g_return_if_fail(ref1 != NULL); adg_dim_set_ref1_explicit(dim, ref1->x, ref1->y); } /** * adg_dim_set_ref1_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref1: a named pair in @model * * Binds #AdgDim:ref1 to the @ref1 named pair of @model. If @model * is NULL, the point will be unset. In any case, * the old point is silently discarded, unreferencing its model * if that point was bound to a named pair (hence, possibly destroying * the model if this was the last reference). * * The assignment is lazy so @ref1 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref1_from_model(AdgDim *dim, AdgModel *model, const gchar *ref1) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref1); adg_dim_set_ref1(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref1: * @dim: an #AdgDim * * Gets the #AdgDim:ref1 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the first reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref1(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref1; } /** * adg_dim_set_ref2: * @dim: an #AdgDim * @ref2: the new point to use as second reference * * Sets the #AdgDim:ref2 property to @ref2. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @ref2 can be NULL, in which case * the point is destroyed. * * Since: 1.0 **/ void adg_dim_set_ref2(AdgDim *dim, const AdgPoint *ref2) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "ref2", ref2, NULL); } /** * adg_dim_set_ref2_explicit: * @dim: an #AdgDim * @x: x coordinate of the second reference point * @y: y coordinate of the second reference point * * Sets the #AdgDim:ref2 property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_ref2_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_set_ref2_from_pair: * @dim: an #AdgDim * @ref2: the coordinates pair of the second reference point * * Convenient function to set the #AdgDim:ref2 property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_ref2_from_pair(AdgDim *dim, const CpmlPair *ref2) { g_return_if_fail(ref2 != NULL); adg_dim_set_ref2_explicit(dim, ref2->x, ref2->y); } /** * adg_dim_set_ref2_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @ref2: a named pair in @model * * Binds #AdgDim:ref2 to the @ref2 named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, possibly * destroying the model if this was the last reference). * * The assignment is lazy so @ref2 could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_ref2_from_model(AdgDim *dim, AdgModel *model, const gchar *ref2) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, ref2); adg_dim_set_ref2(dim, point); adg_point_destroy(point); } /** * adg_dim_get_ref2: * @dim: an #AdgDim * * Gets the #AdgDim:ref2 point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the second reference point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_ref2(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->ref2; } /** * adg_dim_set_pos: * @dim: an #AdgDim * @pos: the new point to use as position * * Sets the #AdgDim:pos property of @dim to @pos. The old point * is silently discarded, unreferencing its model if that * point was bound to a named pair (hence, possibly destroying * the model if it was the last reference). * * @pos can be NULL, in which case the * point is destroyed. * * Since: 1.0 **/ void adg_dim_set_pos(AdgDim *dim, const AdgPoint *pos) { g_return_if_fail(ADG_IS_DIM(dim)); g_object_set(dim, "pos", pos, NULL); } /** * adg_dim_set_pos_explicit: * @dim: an #AdgDim * @x: x coordinate of the position * @y: y coordinate of the position * * Sets the #AdgDim:pos property to the (@x, @y) explicit * coordinates. The old point is silently discarded, * unreferencing its model if that point was bound to a named * pair (hence, possibly destroying the model if this was the * last reference). * * Since: 1.0 **/ void adg_dim_set_pos_explicit(AdgDim *dim, gdouble x, gdouble y) { AdgPoint *point = adg_point_new(); adg_point_set_pair_explicit(point, x, y); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_set_pos_from_pair: * @dim: an #AdgDim * @pos: the coordinates pair of the position point * * Convenient function to set the #AdgDim:pos property using a * pair instead of explicit coordinates. * * Since: 1.0 **/ void adg_dim_set_pos_from_pair(AdgDim *dim, const CpmlPair *pos) { g_return_if_fail(pos != NULL); adg_dim_set_pos_explicit(dim, pos->x, pos->y); } /** * adg_dim_set_pos_from_model: * @dim: an #AdgDim * @model: the source #AdgModel * @pos: a named pair in @model * * Binds #AdgDim:pos to the @pos named pair of @model. If @model * is NULL, the point will be unset. In any * case, the old point is silently discarded, unreferencing its * model if that point was bound to a named pair (hence, * possibly destroying the model if this was the last reference). * * The assignment is lazy so @pos could be not be present in @model. * Anyway, at the first access to this point an error will be raised * if the named pair is still missing. * * Since: 1.0 **/ void adg_dim_set_pos_from_model(AdgDim *dim, AdgModel *model, const gchar *pos) { AdgPoint *point = adg_point_new(); adg_point_set_pair_from_model(point, model, pos); adg_dim_set_pos(dim, point); adg_point_destroy(point); } /** * adg_dim_get_pos: * @dim: an #AdgDim * * Gets the #AdgDim:pos point of @dim. * * The returned point is internally owned and must not be freed * or modified. Anyway it is not const because a call to * adg_point_update() with the returned value must be able to * modify the internal cache. * * Returns: (transfer none): the position point. * * Since: 1.0 **/ AdgPoint * adg_dim_get_pos(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->pos; } /** * adg_dim_set_level: * @dim: an #AdgDim * @level: the new level * * Sets a new level for this dimension. The level is used to * stack the quotes using a spacing value from dim_style * (specified in global space). * * Since: 1.0 **/ void adg_dim_set_level(AdgDim *dim, gdouble level) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->level = level; g_object_notify((GObject *) dim, "level"); } /** * adg_dim_get_level: * @dim: an #AdgDim * * Gets the level of this dimension. * * Returns: the level value. * * Since: 1.0 **/ gdouble adg_dim_get_level(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), 0); data = adg_dim_get_instance_private(dim); return data->level; } /** * adg_dim_set_outside: * @dim: an #AdgDim * @outside: the new outside state * * Sets a new state for the #AdgDim:outside flag: check the property * documentation for further details. * * Since: 1.0 **/ void adg_dim_set_outside(AdgDim *dim, AdgThreeState outside) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_outside(dim, outside)) g_object_notify((GObject *) dim, "outside"); } /** * adg_dim_get_outside: * @dim: an #AdgDim * * Gets the state of the #AdgDim:outside property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_outside(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->outside; } /** * adg_dim_set_detached: * @dim: an #AdgDim * @detached: the new detached state * * Sets a new state for the #AdgDim:detached flag: check the property * documentation for further details. * * This is used only by dimensions where detaching has meaning. * In some cases, such as with #AdgRDim dimensions, this property is * not used. * * Since: 1.0 **/ void adg_dim_set_detached(AdgDim *dim, AdgThreeState detached) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_detached(dim, detached)) g_object_notify((GObject *) dim, "detached"); } /** * adg_dim_get_detached: * @dim: an #AdgDim * * Gets the state of the #AdgDim:detached property: check the property * documentation for further details. * * Returns: the current flag state. * * Since: 1.0 **/ AdgThreeState adg_dim_get_detached(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), ADG_THREE_STATE_UNKNOWN); data = adg_dim_get_instance_private(dim); return data->detached; } /** * adg_dim_set_value: * @dim: an #AdgDim * @value: (allow-none): the value text * - * Explicitely sets the text to use as value. If @value + * Explicitly sets the text to use as value. If @value * is NULL or was never set, an automatic * text is calculated using the format specified in the current * #AdgDimStyle and getting its value by calling * the default_value virtual method. * * Inside the template string, the "<>" tag (or whatever specified * by the #AdgDimStyle:number-tag property) is substituted with the * string returned by default_value. * * Since: 1.0 **/ void adg_dim_set_value(AdgDim *dim, const gchar *value) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_value(dim, value)) g_object_notify((GObject *) dim, "value"); } /** * adg_dim_get_value: * @dim: an #AdgDim * * Gets the value text. The string is internally owned and * must not be freed or modified. * * Returns: (transfer none): the value text. * * Since: 1.0 **/ const gchar * adg_dim_get_value(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->value; } /** * adg_dim_get_text: * @dim: an #AdgDim * @value: the raw value of the quote * * Gets the final text to show as nominal value into the quote. The string is * the same returned by adg_dim_get_value() with the tag properly expanded. * * The string substituted to the tag is formatted according to the * #AdgDimStyle:number-format and #AdgDimStyle:number-arguments properties. * See the #AdgDimStyle documentation for further details. * * Returns: (transfer full): the final text of the quote. * * Since: 1.0 **/ gchar * adg_dim_get_text(AdgDim *dim, gdouble value) { AdgDimStyle *dim_style; const gchar *format; const gchar *arguments; AdgDimReplaceData data; gchar *raw, *result; GRegex *regex; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); dim_style = adg_dim_get_dim_style(dim); if (dim_style == NULL) { dim_style = (AdgDimStyle *) adg_entity_style((AdgEntity *) dim, adg_dim_get_dim_dress(dim)); } format = adg_dim_style_get_number_format(dim_style); if (format == NULL) { return NULL; } arguments = adg_dim_style_get_number_arguments(dim_style); if (arguments == NULL) { return g_strdup(format); } /* Expand the values */ data.dim_style = dim_style; data.value = value; data.format = format; data.argument = arguments; data.regex = g_regex_new("(?NULL * as @min to disable it. * * Since: 1.0 **/ void adg_dim_set_min(AdgDim *dim, const gchar *min) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_min(dim, min)) g_object_notify((GObject *) dim, "min"); } /** * adg_dim_get_min: * @dim: an #AdgDim * * Gets the minimum value text or NULL * on minimum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the mimimum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_min(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->min; } /** * adg_dim_set_max: * @dim: an #AdgDim * @max: (allow-none): the new maximum value * * Sets the maximum value. Use NULL * as @max to disable it. * * Since: 1.0 **/ void adg_dim_set_max(AdgDim *dim, const gchar *max) { g_return_if_fail(ADG_IS_DIM(dim)); if (_adg_set_max(dim, max)) g_object_notify((GObject *) dim, "max"); } /** * adg_dim_get_max: * @dim: an #AdgDim * * Gets the maximum value text or NULL * on maximum value disabled. * * The string is internally owned and must not be freed or modified. * * Returns: (transfer none): the maximum value text. * * Since: 1.0 **/ const gchar * adg_dim_get_max(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->max; } /** * adg_dim_get_quote: * @dim: an #AdgDim * * Gets the quote entity, if any. This function is valid only after * the #AdgDim implementation of the arrange() virtual method has * been called. * * The returned entity is owned by @dim and should not be * modified or freed. * * * This function is only useful in new dimension implementations. * * * Returns: (transfer none): the quote entity. * * Since: 1.0 **/ AdgAlignment * adg_dim_get_quote(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->quote.entity; } /** * adg_dim_quote_angle: * @dim: an #AdgDim * @angle: an angle (in radians) * * * This function is only useful in new dimension implementations. * * * Converts @angle accordling to the style of @dim. Any quote angle * should be validated by this method because every dimensioning * style has its own convention regardling the text rotation. * * Returns: the angle to use (always in radians). * * Since: 1.0 **/ gdouble adg_dim_quote_angle(AdgDim *dim, gdouble angle) { AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), angle); klass = ADG_DIM_GET_CLASS(dim); if (klass->quote_angle == NULL) return angle; return klass->quote_angle(angle); } /** * adg_dim_has_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Checks if the geometry data of @dim has already been computed. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_has_geometry(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); return data->geometry.computed; } /** * adg_dim_switch_geometry: * @dim: an #AdgDim * @computed: the new computed state * * * This function is only useful in new dimension implementations. * * * Sets the computed state of @dim to @computed. This is an internal flag that * keeps track of when the geometry data is up to date. * * Since: 1.0 **/ void adg_dim_switch_geometry(AdgDim *dim, gboolean computed) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); data->geometry.computed = computed; } /** * adg_dim_get_geometry_notice: * @dim: an #AdgDim * * Gets the geometry message of @dim, i.e. a notice that explains why a * geometry computation has failed. This message can be used for debugging * purposes, e.g. to know why it is not possible to draw a dimension. * * Returns: the geometry notice or NULL on no notification. * * Since: 1.0 **/ const gchar * adg_dim_get_geometry_notice(AdgDim *dim) { AdgDimPrivate *data; g_return_val_if_fail(ADG_IS_DIM(dim), NULL); data = adg_dim_get_instance_private(dim); return data->geometry.notice; } /** * adg_dim_set_geometry_notice: * @dim: an #AdgDim * @notice: new notice message * * * This function is only useful in new dimension implementations. * * * Sets the geometry message of @dim to @notice whenever a geometry * computation has failed. This message can later be read with * adg_dim_get_geometry_notice(): see its documentation for further details. * * Since: 1.0 **/ void adg_dim_set_geometry_notice(AdgDim *dim, const gchar *notice) { AdgDimPrivate *data; g_return_if_fail(ADG_IS_DIM(dim)); data = adg_dim_get_instance_private(dim); if (data->geometry.notice != NULL) g_free(data->geometry.notice); data->geometry.notice = g_strdup(notice); } /** * adg_dim_geometry_missing: * @dim: an #AdgDim * @subject: what is missing * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when a reference is missing. * * Since: 1.0 **/ void adg_dim_geometry_missing(AdgDim *dim, const gchar *subject) { gchar *notice; g_return_if_fail(subject != NULL); notice = g_strdup_printf(_("'%s' is missing"), subject); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** * adg_dim_geometry_coincident: * @dim: an #AdgDim * @first: the name of the first point * @second: the name of the second point * @pos: the coordinates of both points * * * This function is only useful in new dimension implementations. * * * Wrapper around adg_dim_set_geometry_notice() that sets a default * notification message when two references that must be different are * coincidents. * * Since: 1.0 **/ void adg_dim_geometry_coincident(AdgDim *dim, const gchar *first, const gchar *second, const CpmlPair *pos) { gchar *notice; g_return_if_fail(first != NULL); g_return_if_fail(second != NULL); g_return_if_fail(pos != NULL); notice = g_strdup_printf(_("'%s' and '%s' cannot be coincident (%lf, %lf)"), first, second, pos->x, pos->y); adg_dim_set_geometry_notice(dim, notice); g_free(notice); } /** * adg_dim_compute_geometry: * @dim: an #AdgDim * * * This function is only useful in new dimension implementations. * * * Updates the geometry data of @dim, i.e. a set of support data (coordinates, * offsets, shifts) needed in the arrange() phase to build up the entity. * * The data is cached, so further calls just return TRUE. Use * adg_entity_invalidate() to force a recomputation. * * Returns: TRUE if the geometry has already been computed, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_dim_compute_geometry(AdgDim *dim) { AdgDimPrivate *data; AdgDimClass *klass; g_return_val_if_fail(ADG_IS_DIM(dim), FALSE); data = adg_dim_get_instance_private(dim); if (data->geometry.computed) return TRUE; - /* compute_geometry virtual method explicitely set to NULL means the + /* compute_geometry virtual method explicitly set to NULL means the * entity does not have any geometry data, so just set computed to TRUE */ klass = ADG_DIM_GET_CLASS(dim); if (klass->compute_geometry != NULL && ! klass->compute_geometry(dim)) return FALSE; data->geometry.computed = TRUE; return TRUE; } static void _adg_global_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->quote.entity) adg_entity_global_changed((AdgEntity *) data->quote.entity); } static void _adg_local_changed(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); if (data->quote.entity) adg_entity_local_changed((AdgEntity *) data->quote.entity); } static void _adg_invalidate(AdgEntity *entity) { AdgDimPrivate *data = adg_dim_get_instance_private((AdgDim *) entity); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } if (data->quote.entity) adg_entity_invalidate((AdgEntity *) data->quote.entity); if (data->ref1) adg_point_invalidate(data->ref1); if (data->ref2) adg_point_invalidate(data->ref2); if (data->pos) adg_point_invalidate(data->pos); data->geometry.computed = FALSE; if (data->geometry.notice != NULL) { g_free(data->geometry.notice); data->geometry.notice = NULL; } if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgDim *dim; AdgDimPrivate *data; AdgEntity *quote_entity; AdgContainer *quote_container; AdgEntity *value_entity; AdgEntity *min_entity; AdgEntity *max_entity; const CpmlPair *shift; cairo_matrix_t map; dim = (AdgDim *) entity; data = adg_dim_get_instance_private(dim); /* Resolve the dim style */ if (data->dim_style == NULL) data->dim_style = (AdgDimStyle *) adg_entity_style(entity, data->dim_dress); if (data->quote.entity == NULL) data->quote.entity = g_object_new(ADG_TYPE_ALIGNMENT, "local-mix", ADG_MIX_NONE, "parent", dim, NULL); quote_entity = (AdgEntity *) data->quote.entity; quote_container = (AdgContainer *) data->quote.entity; if (data->quote.value == NULL) { AdgDimClass *klass; AdgDress dress; const gchar *tag; gchar *value; gchar *text; klass = ADG_DIM_GET_CLASS(dim); dress = adg_dim_style_get_value_dress(data->dim_style); tag = adg_dim_style_get_number_tag(data->dim_style); value = klass->default_value ? klass->default_value(dim) : NULL; data->quote.value = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.value); if (data->value) text = adg_string_replace(data->value, tag, value); else text = g_strdup(value); g_free(value); adg_textual_set_text(data->quote.value, text); g_free(text); } if (data->quote.min == NULL && data->min != NULL) { AdgDress dress = adg_dim_style_get_min_dress(data->dim_style); data->quote.min = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.min); adg_textual_set_text(data->quote.min, data->min); } if (data->quote.max == NULL && data->max != NULL) { AdgDress dress = adg_dim_style_get_max_dress(data->dim_style); data->quote.max = g_object_new(ADG_TYPE_BEST_TEXT, "local-mix", ADG_MIX_PARENT, "font-dress", dress, NULL); adg_container_add(quote_container, (AdgEntity *) data->quote.max); adg_textual_set_text(data->quote.max, data->max); } value_entity = (AdgEntity *) data->quote.value; min_entity = (AdgEntity *) data->quote.min; max_entity = (AdgEntity *) data->quote.max; shift = adg_dim_style_get_quote_shift(data->dim_style); adg_entity_set_global_map(quote_entity, adg_matrix_identity()); adg_entity_global_changed(quote_entity); cairo_matrix_init_translate(&map, 0, shift->y); adg_entity_set_global_map(value_entity, &map); adg_entity_arrange(value_entity); /* Limit values (min and max) */ if (min_entity != NULL || max_entity != NULL) { const CpmlPair *limits_shift; gdouble spacing; CpmlPair size; CpmlPair org_min, org_max; limits_shift = adg_dim_style_get_limits_shift(data->dim_style); spacing = adg_dim_style_get_limits_spacing(data->dim_style); size = adg_entity_get_extents(value_entity)->size; org_min.x = size.x + limits_shift->x; org_min.y = -size.y / 2 + limits_shift->y; org_max = org_min; if (min_entity && max_entity) { /* Prearrange the min entity to get its extents */ adg_entity_arrange(min_entity); size = adg_entity_get_extents(min_entity)->size; org_min.y += (size.y + spacing) / 2; org_max.y = org_min.y - size.y - spacing; } if (min_entity != NULL) { cairo_matrix_init_translate(&map, org_min.x, org_min.y); adg_entity_set_global_map(min_entity, &map); adg_entity_arrange(min_entity); } if (max_entity != NULL) { cairo_matrix_init_translate(&map, org_max.x, org_max.y); adg_entity_set_global_map(max_entity, &map); adg_entity_arrange(max_entity); } } } static gboolean _adg_compute_geometry(AdgDim *dim) { g_warning(_("AdgDim::compute_geometry not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return FALSE; } static gchar * _adg_default_value(AdgDim *dim) { g_warning(_("AdgDim::default_value not implemented for '%s'"), g_type_name(G_TYPE_FROM_INSTANCE(dim))); return g_strdup("undef"); } static gdouble _adg_quote_angle(gdouble angle) { angle = cpml_angle(angle); if (angle > G_PI / 3 || angle <= -G_PI_4 * 3) angle = cpml_angle(angle + G_PI); return angle; } static gboolean _adg_set_outside(AdgDim *dim, AdgThreeState outside) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(outside, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->outside == outside) return FALSE; data->outside = outside; return TRUE; } static gboolean _adg_set_detached(AdgDim *dim, AdgThreeState detached) { AdgDimPrivate *data; g_return_val_if_fail(adg_is_enum_value(detached, ADG_TYPE_THREE_STATE), FALSE); data = adg_dim_get_instance_private(dim); if (data->detached == detached) return FALSE; data->detached = detached; return TRUE; } static gboolean _adg_set_value(AdgDim *dim, const gchar *value) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(value, data->value) == 0) return FALSE; g_free(data->value); data->value = g_strdup(value); if (data->quote.value) { g_object_unref(data->quote.value); data->quote.value = NULL; } return TRUE; } static gboolean _adg_set_min(AdgDim *dim, const gchar *min) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(min, data->min) == 0) return FALSE; g_free(data->min); data->min = g_strdup(min); if (data->quote.min) { g_object_unref(data->quote.min); data->quote.min = NULL; } return TRUE; } static gboolean _adg_set_max(AdgDim *dim, const gchar *max) { AdgDimPrivate *data = adg_dim_get_instance_private(dim); if (g_strcmp0(max, data->max) == 0) return FALSE; g_free(data->max); data->max = g_strdup(max); if (data->quote.max) { g_object_unref(data->quote.max); data->quote.max = NULL; } return TRUE; } static gboolean _adg_replace(const GMatchInfo *match_info, GString *result, gpointer user_data) { AdgDimReplaceData *data; gdouble value; gchar *format; gchar buffer[256]; data = (AdgDimReplaceData *) user_data; value = data->value; if (! adg_dim_style_convert(data->dim_style, &value, *data->argument)) { /* Conversion failed: invalid argument? */ g_return_val_if_reached(TRUE); return TRUE; } format = g_match_info_fetch(match_info, 0); /* This should never happen */ g_return_val_if_fail(format != NULL, TRUE); /* Consume the recently used argument */ ++ data->argument; g_ascii_formatd(buffer, 256, format, value); g_free(format); g_string_append(result, buffer); /* Set the valorized flag */ if (value != 0) { data->valorized = ADG_THREE_STATE_ON; } else if (data->valorized == ADG_THREE_STATE_UNKNOWN) { data->valorized = ADG_THREE_STATE_OFF; } return FALSE; } static gchar * _adg_text_expand(AdgDimReplaceData *data) { GString *result; const gchar *bog, *eog; gchar *string; AdgThreeState valorized; gssize len; valorized = ADG_THREE_STATE_UNKNOWN; result = g_string_new(""); eog = adg_unescaped_strchr(data->format, ')'); /* Expand eventual groups found in the same nesting level */ while ((bog = adg_unescaped_strchr(data->format, '(')) != NULL) { /* If eog precedes bog, it means that bog is in another nest */ if (eog != NULL && eog < bog) { break; } len = bog - data->format; /* Parse template before the bog */ data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); valorized = OR_3S(valorized, data->valorized); data->format += len+1; g_string_append(result, string); g_free(string); /* Recursively expand the group */ string = _adg_text_expand(data); valorized = OR_3S(valorized, data->valorized); g_string_append(result, string); g_free(string); /* Ensure there is a matching closing parenthesis */ if (*data->format != ')') { g_string_free(result, TRUE); g_return_val_if_reached(NULL); return NULL; } /* Skip the closing parenthesis */ ++ data->format; eog = adg_unescaped_strchr(data->format, ')'); } /* Expand until closing parenthesis (End Of Group) or '\0' */ len = eog == NULL ? strlen(data->format) : (eog - data->format); data->valorized = ADG_THREE_STATE_UNKNOWN; string = g_regex_replace_eval(data->regex, data->format, len, 0, 0, _adg_replace, data, NULL); data->format += len; g_string_append(result, string); g_free(string); /* Store the final valorized state */ valorized = OR_3S(valorized, data->valorized); data->valorized = valorized; /* Drop the result only if we are inside a group */ if (*data->format && valorized == ADG_THREE_STATE_OFF) { g_string_free(result, TRUE); return g_strdup(""); } return g_string_free(result, FALSE); } diff --git a/src/adg/adg-edges.c b/src/adg/adg-edges.c index 6f91aef0..f8479855 100644 --- a/src/adg/adg-edges.c +++ b/src/adg/adg-edges.c @@ -1,630 +1,630 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-edges * @short_description: A model with the edges of another model * * The #AdgEdges can be used to render the edges of a yet existing * #AdgTrail source. It is useful for any part made by revolution, * where the shape is symmetric along a specific axis and thus the * edge lines can be easily computed. * * The trail can be set by changing the #AdgEdges:source property * or the relevant APIs. If the trail changes, a recomputation * can be forced by calling the adg_model_clear() method. * * The angle of the axis is implied to pass through the (0,0) point * and has an angle of #AdgEdges:axis-angle radiants. The default * is a 0 radiant angle, meaning the y=0 axis is assumed. * * Since: 1.0 **/ /** * AdgEdges: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include #include "adg-edges.h" #include "adg-edges-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_edges_parent_class) #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_edges_parent_class) #define DEFAULT_CRITICAL_ANGLE (G_PI / 180) G_DEFINE_TYPE_WITH_PRIVATE(AdgEdges, adg_edges, ADG_TYPE_TRAIL) enum { PROP_0, PROP_SOURCE, PROP_CRITICAL_ANGLE, PROP_AXIS_ANGLE }; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_clear (AdgModel *model); static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail); static void _adg_unset_source (AdgEdges *edges); static void _adg_clear_cairo_path (AdgEdges *edges); static GSList * _adg_get_vertices (GSList *vertices, CpmlSegment *segment, gdouble threshold); static GSList * _adg_optimize_vertices (GSList *vertices); static GArray * _adg_path_build (const GSList *vertices); static void _adg_path_transform (GArray *path_data, const cairo_matrix_t*map); static void adg_edges_class_init(AdgEdgesClass *klass) { GObjectClass *gobject_class; AdgModelClass *model_class; AdgTrailClass *trail_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; model_class = (AdgModelClass *) klass; trail_class = (AdgTrailClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; model_class->clear = _adg_clear; trail_class->get_cairo_path = _adg_get_cairo_path; param = g_param_spec_object("source", P_("Source"), P_("The source from which the edges should be computed from"), ADG_TYPE_TRAIL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property(gobject_class, PROP_SOURCE, param); param = g_param_spec_double("axis-angle", P_("Axis Angle"), P_("The angle of the axis of the source trail: it is implied this axis passes through (0,0)"), -G_PI, G_PI, 0, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_AXIS_ANGLE, param); param = g_param_spec_double("critical-angle", P_("Critical Angle"), P_("The angle that defines which corner generates an edge (if the corner angle is greater than this critical angle) and which edge is ignored"), 0, G_PI, DEFAULT_CRITICAL_ANGLE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CRITICAL_ANGLE, param); } static void adg_edges_init(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); data->source = NULL; data->critical_angle = DEFAULT_CRITICAL_ANGLE; data->axis_angle = 0; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.array = NULL; } static void _adg_dispose(GObject *object) { AdgEdges *edges = (AdgEdges *) object; adg_edges_set_source(edges, NULL); if (_ADG_OLD_OBJECT_CLASS->dispose != NULL) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { _adg_clear_cairo_path((AdgEdges *) object); if (_ADG_OLD_OBJECT_CLASS->finalize != NULL) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgEdges *edges = (AdgEdges *) object; AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); switch (prop_id) { case PROP_SOURCE: g_value_set_object(value, data->source); break; case PROP_AXIS_ANGLE: g_value_set_double(value, data->axis_angle); break; case PROP_CRITICAL_ANGLE: g_value_set_double(value, data->critical_angle); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEdges *edges = (AdgEdges *) object; AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); gpointer tmp_pointer; gdouble tmp_double; switch (prop_id) { case PROP_SOURCE: tmp_pointer = data->source; data->source = g_value_get_object(value); if (tmp_pointer != data->source) { if (data->source) g_object_weak_ref((GObject *) data->source, (GWeakNotify) _adg_unset_source, object); if (tmp_pointer) g_object_weak_unref((GObject *) tmp_pointer, (GWeakNotify) _adg_unset_source, object); } _adg_clear((AdgModel *) object); break; case PROP_AXIS_ANGLE: tmp_double = g_value_get_double(value); if (data->axis_angle != tmp_double) { data->axis_angle = tmp_double; _adg_clear_cairo_path(edges); } break; case PROP_CRITICAL_ANGLE: tmp_double = g_value_get_double(value); if (data->critical_angle != tmp_double) { data->critical_angle = tmp_double; _adg_clear_cairo_path(edges); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_edges_new: * * Creates a new undefined model to keep track of the edges of * another model. You should at least set the referred #AdgTrail * with adg_edges_set_source(). * * Returns: the newly created edges model * * Since: 1.0 **/ AdgEdges * adg_edges_new(void) { return g_object_new(ADG_TYPE_EDGES, NULL); } /** * adg_edges_new_with_source: * @source: (transfer none): the new source #AdgTrail * - * Creates a new edges model explicitely specifying the source trail. + * Creates a new edges model explicitly specifying the source trail. * The returned object will own a weak reference on @source. * * Returns: the newly created edges model * * Since: 1.0 **/ AdgEdges * adg_edges_new_with_source(AdgTrail *source) { return g_object_new(ADG_TYPE_EDGES, "source", source, NULL); } /** * adg_edges_get_source: * @edges: an #AdgEdges * * Gets the source #AdgTrail of this @edges model. * The returned object is owned by @edges and should not be * freed or modified. * * Returns: (transfer none): the requested #AdgTrail or NULL on errors. * * Since: 1.0 **/ AdgTrail * adg_edges_get_source(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), NULL); data = adg_edges_get_instance_private(edges); return data->source; } /** * adg_edges_set_source: * @edges: an #AdgEdges * @source: (transfer none): the new source #AdgTrail * * Sets @source as the source trail for @edges. * After the call, @edges will own a weak reference on @source. * * Since: 1.0 **/ void adg_edges_set_source(AdgEdges *edges, AdgTrail *source) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "source", source, NULL); } /** * adg_edges_set_axis_angle: * @edges: an #AdgEdges * @angle: the new angle (in radians) * * Sets the axis angle of @edges to @angle, basically setting * the #AdgEdges:axis-angle property. All the resulting edge * lines will be normal to this axis. * * It is implied the axis will pass through the (0,0) point, * so the underlying trail should be constructed accordingly. * * Since: 1.0 **/ void adg_edges_set_axis_angle(AdgEdges *edges, gdouble angle) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "axis-angle", angle, NULL); } /** * adg_edges_get_axis_angle: * @edges: an #AdgEdges * * Gets the angle of the supposed axis of @edges. Refer to * adg_edges_set_axis_angle() for details of what this parameter * is used for. * * Returns: the value (in radians) of the axis angle * * Since: 1.0 **/ gdouble adg_edges_get_axis_angle(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), 0); data = adg_edges_get_instance_private(edges); return data->axis_angle; } /** * adg_edges_set_critical_angle: * @edges: an #AdgEdges * @angle: the new angle (in radians) * * Sets the critical angle of @edges to @angle, basically setting * the #AdgEdges:critical-angle property. * * The critical angle defines what corner should generate an edge and * what not. Typical values are close to 0, being 0 the lowest angle * where every corner generates an edge. * * Since: 1.0 **/ void adg_edges_set_critical_angle(AdgEdges *edges, gdouble angle) { g_return_if_fail(ADG_IS_EDGES(edges)); g_object_set(edges, "critical-angle", angle, NULL); } /** * adg_edges_get_critical_angle: * @edges: an #AdgEdges * * Gets the current critical angle of @edges. Refer to * adg_edges_set_critical_angle() for details of what this parameter * is used for. * * Returns: the value (in radians) of the critical angle * * Since: 1.0 **/ gdouble adg_edges_get_critical_angle(AdgEdges *edges) { AdgEdgesPrivate *data; g_return_val_if_fail(ADG_IS_EDGES(edges), 0); data = adg_edges_get_instance_private(edges); return data->critical_angle; } static void _adg_clear(AdgModel *model) { _adg_clear_cairo_path((AdgEdges *) model); if (_ADG_OLD_MODEL_CLASS->clear != NULL) _ADG_OLD_MODEL_CLASS->clear(model); } static cairo_path_t * _adg_get_cairo_path(AdgTrail *trail) { AdgEdges *edges; AdgEdgesPrivate *data; gdouble threshold; CpmlSegment segment; GSList *vertices; cairo_matrix_t map; edges = (AdgEdges *) trail; data = adg_edges_get_instance_private(edges); /* Check for cached path */ if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) return &data->cairo.path; _adg_clear_cairo_path((AdgEdges *) trail); if (data->source != NULL) { gint n; /* The threshold is squared because the _adg_get_vertices() * function uses cpml_pair_squared_distance() against the * two vectors of every corner to avoid sqrt()ing everything */ threshold = sin(data->critical_angle); threshold *= threshold * 2; vertices = NULL; for (n = 1; adg_trail_put_segment(data->source, n, &segment); ++ n) { vertices = _adg_get_vertices(vertices, &segment, threshold); } /* Rotate all the vertices so the axis will always be on y=0: * this is mainly needed to not complicate the _adg_path_build() * code which assumes the y=0 axis is in effect */ cairo_matrix_init_rotate(&map, -data->axis_angle); g_slist_foreach(vertices, (GFunc) cpml_pair_transform, &map); vertices = _adg_optimize_vertices(vertices); data->cairo.array = _adg_path_build(vertices); g_slist_foreach(vertices, (GFunc) g_free, NULL); g_slist_free(vertices); /* Reapply the inverse of the previous transformation to * move the vertices to their original positions */ cairo_matrix_invert(&map); _adg_path_transform(data->cairo.array, &map); data->cairo.path.status = CAIRO_STATUS_SUCCESS; data->cairo.path.data = (cairo_path_data_t *) (data->cairo.array)->data; data->cairo.path.num_data = (data->cairo.array)->len; } return &data->cairo.path; } static void _adg_unset_source(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); data->source = NULL; } static void _adg_clear_cairo_path(AdgEdges *edges) { AdgEdgesPrivate *data = adg_edges_get_instance_private(edges); if (data->cairo.array != NULL) { g_array_free(data->cairo.array, TRUE); data->cairo.array = NULL; } data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = NULL; data->cairo.path.num_data = 0; } /** * _adg_get_vertices: * @vertices: a #GSList * @segment: a #CpmlSegment * @threshold: a theshold value * * Collects a list of #CpmlPair corners where the angle has a minimum * threshold incidence of @threshold. The threshold is considered as * the squared distance between the two unit vectors, the one before * and the one after every corner. * * Returns: the original #GSList with new vertices appended. * * Since: 1.0 **/ static GSList * _adg_get_vertices(GSList *vertices, CpmlSegment *segment, gdouble threshold) { CpmlPrimitive primitive; CpmlVector old, new; CpmlPair pair; cpml_primitive_from_segment(&primitive, segment); /* The first vector starts undefined, so it will always be * included (the squared distance between any vector and an * undefined vector will always be greater than threshold) */ old.x = old.y = 0; do { cpml_vector_set_length(&old, 1); cpml_primitive_put_vector_at(&primitive, 0, &new); cpml_vector_set_length(&new, 1); /* Vertical vectors are always added, as they represent * a vertical side and could be filleted, thus skipping * the edge detection */ if (new.x == 0 || cpml_pair_squared_distance(&old, &new) > threshold) { cpml_primitive_put_pair_at(&primitive, 0, &pair); vertices = g_slist_append(vertices, cpml_pair_dup(&pair)); } cpml_primitive_put_vector_at(&primitive, 1, &old); } while (cpml_primitive_next(&primitive)); return vertices; } /* Removes adjacent vertices lying on the same edge */ static GSList * _adg_optimize_vertices(GSList *vertices) { GSList *vertex, *old_vertex; CpmlPair *pair, *old_pair; /* Check for empty list */ if (vertices == NULL) return vertices; old_vertex = vertices; while ((vertex = old_vertex->next) != NULL) { pair = vertex->data; old_pair = old_vertex->data; if (pair->x != old_pair->x) { old_vertex = vertex; continue; } if (old_pair->y < pair->y) { /* Preserve the old vertex and remove the current one */ g_free(pair); vertices = g_slist_delete_link(vertices, vertex); } else { /* Preserve the current vertex and remove the old one */ g_free(old_pair); vertices = g_slist_delete_link(vertices, old_vertex); old_vertex = vertex; } } return vertices; } static GArray * _adg_path_build(const GSList *vertices) { cairo_path_data_t line[4]; GArray *array; const GSList *vertex, *vertex2; const CpmlPair *pair, *pair2; line[0].header.type = CPML_MOVE; line[0].header.length = 2; line[2].header.type = CPML_LINE; line[2].header.length = 2; array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t)); vertex = vertices; while (vertex != NULL) { pair = vertex->data; vertex = vertex->next; vertex2 = vertex; while (vertex2 != NULL) { pair2 = vertex2->data; if (pair->x == pair2->x) { /* Opposite vertex found: append a line in the path * and quit from this loop */ cpml_pair_to_cairo(pair, &line[1]); cpml_pair_to_cairo(pair2, &line[3]); array = g_array_append_vals(array, line, G_N_ELEMENTS(line)); break; } vertex2 = vertex2->next; } } return array; } static void _adg_path_transform(GArray *path_data, const cairo_matrix_t *map) { guint n; cairo_path_data_t *data; /* Only the odd items are transformed: the even ones are either * header items, CPML_MOVE and CPML_LINE alternatively */ for (n = 1; n < path_data->len; n += 2) { data = &g_array_index(path_data, cairo_path_data_t, n); cairo_matrix_transform_point(map, &data->point.x, &data->point.y); } } diff --git a/src/adg/adg-entity.c b/src/adg/adg-entity.c index e2f5a468..0e5b3f56 100644 --- a/src/adg/adg-entity.c +++ b/src/adg/adg-entity.c @@ -1,1401 +1,1401 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-entity * @short_description: The base class for renderable objects * * This abstract class provides the base for all renderable objects. * * To create a drawing you usually create entities by calling their * specific constructors and add them to a single #AdgCanvas * instance. When cleaning up, you therefore need to destroy only * that canvas instance with adg_entity_destroy(): this in turn * will destroy every contained entity. This is pretty similar to * how the GTK+ world works: you add #GtkWidget instances to a * single #GtkWindow and destroy only that window when finished. * * To provide a proper #AdgEntity derived type, you must at least * implement its arrange() and render() virtual methods. Also, if * you are using some sort of caching, ensure to clear it in the * invalidate() method. * * Since: 1.0 **/ /** * AdgEntity: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ /** * AdgEntityClass: - * @destroy: when a destroy request has been explicitely requested + * @destroy: when a destroy request has been explicitly requested * @parent_set: called whenever the parent of an entity has changed * @global_changed: the global matrix has been invalidated * @local_changed: the local matrix has been invalidated * @invalidate: invalidating callback, used to clear the internal cache * @arrange: prepare the layout and fill the extents struct * @render: rendering callback, it must be implemented by every entity * * Any entity (if not abstract) must implement at least the @render method. * The other signal handlers can be overriden to provide custom behaviors * and usually must chain up the original handler. * * Since: 1.0 **/ /** * AdgEntityCallback: * @entity: an #AdgEntity * @user_data: a general purpose pointer * * Callback used when inspecting or browsing entities. For example, * it is passed to adg_model_foreach_dependency() to perform an * operation on all the entities depending on an #AdgModel. * * Since: 1.0 **/ #include "adg-internal.h" #if GTK3_ENABLED || GTK2_ENABLED #include #endif #include "adg-container.h" #include "adg-table.h" #include "adg-title-block.h" #include #include "adg-dress.h" #include "adg-style.h" #include "adg-model.h" #include "adg-point.h" #include "adg-cairo-fallback.h" #include "adg-entity-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_entity_parent_class) G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(AdgEntity, adg_entity, G_TYPE_INITIALLY_UNOWNED) enum { PROP_0, PROP_FLOATING, PROP_PARENT, PROP_GLOBAL_MAP, PROP_LOCAL_MAP, PROP_LOCAL_MIX }; enum { DESTROY, PARENT_SET, GLOBAL_CHANGED, LOCAL_CHANGED, INVALIDATE, ARRANGE, RENDER, LAST_SIGNAL }; static void _adg_dispose (GObject *object); static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_destroy (AdgEntity *entity); static void _adg_set_parent (AdgEntity *entity, AdgEntity *parent); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_real_invalidate (AdgEntity *entity); static void _adg_real_arrange (AdgEntity *entity); static void _adg_real_render (AdgEntity *entity, cairo_t *cr); static guint _adg_signals[LAST_SIGNAL] = { 0 }; static gboolean _adg_show_extents = FALSE; static void adg_entity_class_init(AdgEntityClass *klass) { GObjectClass *gobject_class; GParamSpec *param; GClosure *closure; GType param_types[1]; gobject_class = (GObjectClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; klass->destroy = _adg_destroy; klass->parent_set = NULL; klass->global_changed = _adg_global_changed; klass->local_changed = _adg_local_changed; klass->invalidate = NULL; klass->arrange= NULL; klass->render = NULL; param = g_param_spec_boolean("floating", P_("Floating Entity"), P_("Flag that includes (FALSE) or excludes (TRUE) this entity from the computation of the parent entity extents"), FALSE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FLOATING, param); param = g_param_spec_object("parent", P_("Parent Entity"), P_("The parent entity of this entity or NULL if this is a top-level entity"), ADG_TYPE_ENTITY, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_PARENT, param); param = g_param_spec_boxed("global-map", P_("Global Map"), P_("The transformation to be combined with the parent ones to get the global matrix"), CAIRO_GOBJECT_TYPE_MATRIX, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_GLOBAL_MAP, param); param = g_param_spec_boxed("local-map", P_("Local Map"), P_("The local transformation that could be used to compute the local matrix in the way specified by the #AdgEntity:local-mix property"), CAIRO_GOBJECT_TYPE_MATRIX, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LOCAL_MAP, param); param = g_param_spec_enum("local-mix", P_("Local Mix Method"), P_("Define how the local maps of the entity and its ancestors should be combined to get the local matrix"), ADG_TYPE_MIX, ADG_MIX_ANCESTORS, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_LOCAL_MIX, param); /** * AdgEntity::destroy: * @entity: an #AdgEntity * - * Emitted to explicitely destroy @entity. It unreferences + * Emitted to explicitly destroy @entity. It unreferences * @entity so that will be destroyed, unless the caller owns * an additional references added with g_object_ref(). * * In the usual case, this is equivalent of calling * g_object_unref() on @entity but, for composite entities or * containers, the destroy signal is propagated to the children. * * Since: 1.0 **/ _adg_signals[DESTROY] = g_signal_new("destroy", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, destroy), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::parent-set: * @entity: an #AdgEntity * @old_parent: the old parent * * Emitted after the parent entity has changed. The new parent * can be inspected using adg_entity_get_parent(). * * It is allowed for both old and new parent to * be NULL. * * Since: 1.0 **/ _adg_signals[PARENT_SET] = g_signal_new("parent-set", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, parent_set), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, ADG_TYPE_ENTITY); /** * AdgEntity::global-changed: * @entity: an #AdgEntity * * Emitted when the global map of @entity or any of its parent * has changed. The default handler will compute the new global * matrix, updating the internal cache. * * This signal has lazy emission, i.e. it is emitted only when * the global matrix is requested, typically in the arrange phase. * * Since: 1.0 **/ _adg_signals[GLOBAL_CHANGED] = g_signal_new("global-changed", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, global_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::local-changed: * @entity: an #AdgEntity * * Emitted when the local map of @entity or any of its parent * has changed. The default handler will compute the new local * matrix, updating the internal cache. * * This signal has lazy emission, i.e. it is emitted only when * the local matrix is requested, typically in the arrange phase. * * Since: 1.0 **/ _adg_signals[LOCAL_CHANGED] = g_signal_new("local-changed", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(AdgEntityClass, local_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * AdgEntity::invalidate: * @entity: an #AdgEntity * * Invalidates the whole @entity, that is resets all the cache * (if present) built during the #AdgEntity::arrange signal. * The resulting state is a clean entity, similar to what you * have just before the first rendering. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_invalidate), NULL, NULL); _adg_signals[INVALIDATE] = g_signal_newv("invalidate", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, param_types); /** * AdgEntity::arrange: * @entity: an #AdgEntity * * Arranges the layout of @entity, updating the cache if necessary, * and computes the extents of @entity. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_arrange), NULL, NULL); _adg_signals[ARRANGE] = g_signal_newv("arrange", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, param_types); /** * AdgEntity::render: * @entity: an #AdgEntity * @cr: a #cairo_t drawing context * * Causes the rendering of @entity on @cr. A render signal will * automatically emit #AdgEntity::arrange just before the real * rendering on the cairo context. * * Since: 1.0 **/ closure = g_cclosure_new(G_CALLBACK(_adg_real_render), NULL, NULL); param_types[0] = G_TYPE_POINTER; _adg_signals[RENDER] = g_signal_newv("render", ADG_TYPE_ENTITY, G_SIGNAL_RUN_LAST, closure, NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, param_types); } static void adg_entity_init(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); data->floating = FALSE; data->parent = NULL; cairo_matrix_init_identity(&data->global_map); cairo_matrix_init_identity(&data->local_map); data->local_mix = ADG_MIX_ANCESTORS; data->hash_styles = NULL; data->global.is_defined = FALSE; adg_matrix_copy(&data->global.matrix, adg_matrix_null()); data->local.is_defined = FALSE; adg_matrix_copy(&data->local.matrix, adg_matrix_null()); data->extents.is_defined = FALSE; } static void _adg_dispose(GObject *object) { AdgEntity *entity = (AdgEntity *) object; AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* This call will emit a "notify" signal for parent. * Consequentially, the references to the old parent is dropped. */ adg_entity_set_parent(entity, NULL); if (data->hash_styles != NULL) { g_hash_table_destroy(data->hash_styles); data->hash_styles = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgEntity *entity = (AdgEntity *) object; AdgEntityPrivate *data = adg_entity_get_instance_private(entity); switch (prop_id) { case PROP_FLOATING: g_value_set_boolean(value, data->floating); break; case PROP_PARENT: g_value_set_object(value, data->parent); break; case PROP_GLOBAL_MAP: g_value_set_boxed(value, &data->global_map); break; case PROP_LOCAL_MAP: g_value_set_boxed(value, &data->local_map); break; case PROP_LOCAL_MIX: g_value_set_enum(value, data->local_mix); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgEntityPrivate *data = adg_entity_get_instance_private((AdgEntity *) object); switch (prop_id) { case PROP_FLOATING: data->floating = g_value_get_boolean(value); break; case PROP_PARENT: _adg_set_parent((AdgEntity *) object, (AdgEntity *) g_value_get_object(value)); break; case PROP_GLOBAL_MAP: adg_matrix_copy(&data->global_map, g_value_get_boxed(value)); data->global.is_defined = FALSE; break; case PROP_LOCAL_MAP: adg_matrix_copy(&data->local_map, g_value_get_boxed(value)); data->local.is_defined = FALSE; break; case PROP_LOCAL_MIX: data->local_mix = g_value_get_enum(value); data->local.is_defined = FALSE; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_switch_extents: * @state: new extents state * * Strokes (if @state is TRUE) a rectangle * around every entity to show their extents. Useful for * debugging purposes. * * Since: 1.0 **/ void adg_switch_extents(gboolean state) { _adg_show_extents = state; } /** * adg_entity_destroy: * @entity: an #AdgEntity * * Emits the #AdgEntity::destroy signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_destroy(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[DESTROY], 0); } /** * adg_entity_switch_floating: * @entity: an #AdgEntity * @new_state: the new floating state * * Sets or resets the floating state of @entity. * * By default all entities are not floating. When an entity is "floating", its * extents does not concur on increasing the extents of its own container. In * other words, during the arrange phase #AdgContainer only considers the * non-floating children to compute its extents. In particular, this affects * how adg_canvas_autoscale() works: all floating entities are not taken into * consideration. * * A typical example is the title block or any other annotation not dependent * from the model for positioning. * * Since: 1.0 **/ void adg_entity_switch_floating(AdgEntity *entity, gboolean new_state) { AdgEntityPrivate *data; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(adg_is_boolean_value(new_state)); data = adg_entity_get_instance_private(entity); data->floating = new_state; } /** * adg_entity_has_floating: * @entity: an #AdgEntity * * Checks if @entity has the floating state enabled. * * See adg_entity_switch_floating() for a description of what the floating * state is. * * Returns: the current state of the floating flag. * * Since: 1.0 **/ gboolean adg_entity_has_floating(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), FALSE); data = adg_entity_get_instance_private(entity); return data->floating; } /** * adg_entity_get_canvas: * @entity: an #AdgEntity * * Walks on the @entity hierarchy and gets the first parent of @entity, * that is the first #AdgCanvas instance. The returned object is * owned by @entity and should not be freed or modified. * * Returns: (transfer none): the requested canvas or NULL on errors or if there is no #AdgCanvas in the @entity hierarchy. * * Since: 1.0 **/ AdgCanvas * adg_entity_get_canvas(AdgEntity *entity) { g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); while (entity) { if (ADG_IS_CANVAS(entity)) return (AdgCanvas *) entity; entity = adg_entity_get_parent(entity); } return NULL; } /** * adg_entity_set_parent: * @entity: an #AdgEntity * @parent: the parent entity * * * This function is only useful in entity implementations. * * * Sets a new parent on @entity. Changing the @parent of an entity * emits the #AdgEntity::parent-set signal on it. * * There is no reference management at this level: they should be * handled at a higher level, e.g. by #AdgContainer. * * Since: 1.0 **/ void adg_entity_set_parent(AdgEntity *entity, AdgEntity *parent) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "parent", parent, NULL); } /** * adg_entity_get_parent: * @entity: an #AdgEntity * * Gets the parent of @entity. The returned object is owned * by @entity and should not be freed or modified. * * Returns: (transfer none): the parent entity or NULL on errors or if @entity is a toplevel. * * Since: 1.0 **/ AdgEntity * adg_entity_get_parent(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return data->parent; } /** * adg_entity_set_global_map: * @entity: an #AdgEntity object * @map: the new map * * Sets the new global transformation of @entity to @map: * the old map is discarded. If @map is NULL, * the global map is left unchanged. * * Since: 1.0 **/ void adg_entity_set_global_map(AdgEntity *entity, const cairo_matrix_t *map) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "global-map", map, NULL); } /** * adg_entity_transform_global_map: * @entity: an #AdgEntity object * @transformation: the transformation to apply * @mode: how @transformation should be applied * * Convenient function to change the global map of @entity by * applying @tranformation using the @mode operator. This is * logically equivalent to the following: * * * cairo_matrix_t map; * adg_matrix_copy(&map, adg_entity_get_global_map(entity)); * adg_matrix_transform(&map, transformation, mode); * adg_entity_set_global_map(entity, &map); * * * Since: 1.0 **/ void adg_entity_transform_global_map(AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode) { AdgEntityPrivate *data; cairo_matrix_t map; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(transformation != NULL); data = adg_entity_get_instance_private(entity); adg_matrix_copy(&map, &data->global_map); adg_matrix_transform(&map, transformation, mode); g_object_set(entity, "global-map", &map, NULL); } /** * adg_entity_get_global_map: * @entity: an #AdgEntity object * * Gets the transformation to be used to compute the global matrix * of @entity. * * Returns: the requested map or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_global_map(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->global_map; } /** * adg_entity_get_global_matrix: * @entity: an #AdgEntity object * * Gets the current global matrix of @entity. The returned value * is owned by @entity and should not be changed or freed. * * The global matrix is computed in the arrange() phase by * combining all the global maps of the @entity hierarchy using * the %ADG_MIX_ANCESTORS method. * * Returns: the global matrix or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_global_matrix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->global.matrix; } /** * adg_entity_set_local_map: * @entity: an #AdgEntity object * @map: the new map * * Sets the new local transformation of @entity to @map: * the old map is discarded. If @map is NULL, * the local map is left unchanged. * * Since: 1.0 **/ void adg_entity_set_local_map(AdgEntity *entity, const cairo_matrix_t *map) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "local-map", map, NULL); } /** * adg_entity_transform_local_map: * @entity: an #AdgEntity object * @transformation: the transformation to apply * @mode: how @transformation should be applied * * Convenient function to change the local map of @entity by * applying @tranformation using the @mode operator. This is * logically equivalent to the following: * * * cairo_matrix_t map; * adg_matrix_copy(&map, adg_entity_get_local_map(entity)); * adg_matrix_transform(&map, transformation, mode); * adg_entity_set_local_map(entity, &map); * * * Since: 1.0 **/ void adg_entity_transform_local_map(AdgEntity *entity, const cairo_matrix_t *transformation, AdgTransformMode mode) { AdgEntityPrivate *data; cairo_matrix_t map; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(transformation != NULL); data = adg_entity_get_instance_private(entity); adg_matrix_copy(&map, &data->local_map); adg_matrix_transform(&map, transformation, mode); g_object_set(entity, "local-map", &map, NULL); } /** * adg_entity_get_local_map: * @entity: an #AdgEntity object * * Gets the transformation to be used to compute the local matrix * of @entity and store it in @map. * * Returns: the requested map or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_local_map(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->local_map; } /** * adg_entity_get_local_matrix: * @entity: an #AdgEntity object * * Gets the current local matrix of @entity. The returned value * is owned by @entity and should not be changed or freed. * * The local matrix is computed in the arrange() phase by * combining all the local maps of the @entity hierarchy using * the method specified by the #AdgEntity:local-mix property. * * Returns: the local matrix or NULL on errors. * * Since: 1.0 **/ const cairo_matrix_t * adg_entity_get_local_matrix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->local.matrix; } /** * adg_entity_set_local_mix: * @entity: an #AdgEntity object * @local_mix: new mix method * * Sets a new local mix method on @entity. The * #AdgEntity:local-mix property defines how the local * matrix must be computed: check out the #AdgMix * documentation to know what are the availables methods * and how they affect the local matrix computation. * * Setting a different local mix method emits an * #AdgEntity::local-changed signal on @entity. * * Since: 1.0 **/ void adg_entity_set_local_mix(AdgEntity *entity, AdgMix local_mix) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_object_set(entity, "local-mix", local_mix, NULL); } /** * adg_entity_get_local_mix: * @entity: an #AdgEntity object * * Gets the local mix method of @entity. Check out the * adg_entity_set_local_mix() documentation to know what the * local mix method is used for. * * Returns: the local mix method of @entity or %ADG_MIX_UNDEFINED on errors * * Since: 1.0 **/ AdgMix adg_entity_get_local_mix(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), ADG_MIX_UNDEFINED); data = adg_entity_get_instance_private(entity); return data->local_mix; } /** * adg_entity_set_extents: * @entity: an #AdgEntity * @extents: the new extents * * * This function is only useful in entity implementations. * * * Sets a new bounding box for @entity. @extents can * be NULL, in which case the extents are unset. * * Since: 1.0 **/ void adg_entity_set_extents(AdgEntity *entity, const CpmlExtents *extents) { AdgEntityPrivate *data; g_return_if_fail(ADG_IS_ENTITY(entity)); data = adg_entity_get_instance_private(entity); if (extents == NULL) data->extents.is_defined = FALSE; else cpml_extents_copy(&data->extents, extents); } /** * adg_entity_get_extents: * @entity: an #AdgEntity * * Gets the bounding box of @entity. The returned struct is * owned by @entity and should not modified or freed. * * This struct specifies the surface portion (in global space * of @entity) occupied by the entity without taking into * account rendering properties such as line thickness or caps. * * The #AdgEntity::arrange signal should be emitted before - * this call (either explicitely trought adg_entity_arrange() + * this call (either explicitly trought adg_entity_arrange() * or implicitely with adg_entity_render()) in order to get * an up to date boundary box. * * Returns: the bounding box of @entity or NULL on errors. * * Since: 1.0 **/ const CpmlExtents * adg_entity_get_extents(AdgEntity *entity) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); return &data->extents; } /** * adg_entity_set_style: * @entity: an #AdgEntity * @dress: a dress style * @style: the new style to use * * Overrides the style of @dress for @entity and its children. * If @style is NULL, any previous * override is removed. * * The new style must still be compatible with @dress: check out * the adg_dress_style_is_compatible() documentation to know * what a compatible style means. * * Since: 1.0 **/ void adg_entity_set_style(AdgEntity *entity, AdgDress dress, AdgStyle *style) { AdgEntityPrivate *data; gpointer p_dress; AdgStyle *old_style; g_return_if_fail(ADG_IS_ENTITY(entity)); data = adg_entity_get_instance_private(entity); if (data->hash_styles == NULL && style == NULL) return; if (data->hash_styles == NULL) data->hash_styles = g_hash_table_new_full(NULL, NULL, NULL, g_object_unref); p_dress = GINT_TO_POINTER(dress); old_style = g_hash_table_lookup(data->hash_styles, p_dress); if (style == old_style) return; if (style == NULL) { g_hash_table_remove(data->hash_styles, p_dress); return; } if (!adg_dress_style_is_compatible(dress, style)) { GType ancestor_type = adg_dress_get_ancestor_type(dress); g_warning(_("%s: '%s' is not compatible with '%s' for '%s' dress (%d)"), G_STRLOC, g_type_name(G_TYPE_FROM_INSTANCE(style)), g_type_name(ancestor_type), adg_dress_get_name(dress), dress); return; } g_object_ref(style); g_hash_table_replace(data->hash_styles, p_dress, style); } /** * adg_entity_get_style: * @entity: an #AdgEntity * @dress: the dress of the style to get * * Gets the overriden @dress style from @entity. This is a kind * of accessor function: for rendering purpose use adg_entity_style() * instead. The returned object is owned by @entity and should not be * freed or modified. * * Returns: (transfer none): the requested style or NULL if the @dress style is not overriden. * * Since: 1.0 **/ AdgStyle * adg_entity_get_style(AdgEntity *entity, AdgDress dress) { AdgEntityPrivate *data; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); data = adg_entity_get_instance_private(entity); if (data->hash_styles == NULL) return NULL; return g_hash_table_lookup(data->hash_styles, GINT_TO_POINTER(dress)); } /** * adg_entity_style: * @entity: an #AdgEntity * @dress: the dress of the style to get * * Gets the style to be used for @entity. @dress specifies which * "family" of style to get. * * The following sequence of checks is performed to get the proper * style, stopping at the first succesfull result: * * * check if the style is directly overriden by this entity, * as returned by adg_entity_get_style(); * check if @entity has a parent, in which case returns the * adg_entity_style() of the parent; * returns the main style with adg_dress_get_fallback(). * * * The returned object is owned by @entity and should not be * freed or modified. * * Returns: (transfer none): the requested style or NULL for transparent dresses or errors. * * Since: 1.0 **/ AdgStyle * adg_entity_style(AdgEntity *entity, AdgDress dress) { AdgStyle *style; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); style = adg_entity_get_style(entity, dress); if (style == NULL) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); if (data->parent != NULL) style = adg_entity_style(data->parent, dress); else style = adg_dress_get_fallback(dress); } return style; } /** * adg_entity_apply_dress: * @entity: an #AdgEntity * @dress: the dress style to apply * @cr: a #cairo_t drawing context * * Convenient function to apply a @dress style (as returned by * adg_entity_style()) to the @cr cairo context. * * Since: 1.0 **/ void adg_entity_apply_dress(AdgEntity *entity, AdgDress dress, cairo_t *cr) { AdgStyle *style; g_return_if_fail(ADG_IS_ENTITY(entity)); g_return_if_fail(cr != NULL); style = adg_entity_style(entity, dress); if (style != NULL) adg_style_apply(style, entity, cr); } /** * adg_entity_global_changed: * @entity: an #AdgEntity * * Emits the #AdgEntity::global-changed signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_global_changed(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0); } /** * adg_entity_local_changed: * @entity: an #AdgEntity * * Emits the #AdgEntity::local-changed signal on @entity and on all of * its children, if any. * * Since: 1.0 **/ void adg_entity_local_changed(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0); } /** * adg_entity_invalidate: * @entity: an #AdgEntity * * Emits the #AdgEntity::invalidate signal on @entity and on all of * its children, if any, clearing the eventual cache stored by the * #AdgEntity::arrange signal and setting the entity state similary * to the just initialized entity. * * Since: 1.0 **/ void adg_entity_invalidate(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[INVALIDATE], 0); } /** * adg_entity_arrange: * @entity: an #AdgEntity * * Emits the #AdgEntity::arrange signal on @entity and all its children, * if any. The arrange call is implicitely called by the * #AdgEntity::render signal but not by adg_entity_get_extents(). * * Since: 1.0 **/ void adg_entity_arrange(AdgEntity *entity) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[ARRANGE], 0); } /** * adg_entity_render: * @entity: an #AdgEntity * @cr: a #cairo_t drawing context * * Emits the #AdgEntity::render signal on @entity and on all of its * children, if any, causing the rendering to the @cr cairo context. * * Since: 1.0 **/ void adg_entity_render(AdgEntity *entity, cairo_t *cr) { g_return_if_fail(ADG_IS_ENTITY(entity)); g_signal_emit(entity, _adg_signals[RENDER], 0, cr); } /** * adg_entity_point: * @entity: an #AdgEntity * @point: the #AdgPoint to define * @new_point: the new #AdgPoint value * * * This function is only useful in entity implementations. * * * A convenient method to set an #AdgPoint owned by @entity. * @old_point is the old value while @new_point is the new value. * It can be used for changing a private #AdgPoint struct, such as: * * * data->point = adg_entity_point(entity, data->point, new_point); * * * This function takes care of the dependencies between @entity and * the eventual models bound to the old and new points. * * @old_point can be NULL, in which case a * clone of @new_point will be returned. Also @new_point can * be NULL, in which case @old_point * is destroyed and NULL will be returned. * * Returns: (transfer full): the new properly defined point * * Since: 1.0 **/ AdgPoint * adg_entity_point(AdgEntity *entity, AdgPoint *old_point, const AdgPoint *new_point) { AdgPoint *point; g_return_val_if_fail(ADG_IS_ENTITY(entity), NULL); point = NULL; if (! adg_point_equal(old_point, new_point)) { AdgModel *old_model, *new_model; old_model = old_point != NULL ? adg_point_get_model(old_point) : NULL; new_model = new_point != NULL ? adg_point_get_model(new_point) : NULL; if (new_model != old_model) { /* Handle model-entity dependencies */ if (new_model != NULL) adg_model_add_dependency(new_model, entity); if (old_model != NULL) adg_model_remove_dependency(old_model, entity); } if (new_point != NULL) point = adg_point_dup(new_point); if (old_point != NULL) adg_point_destroy(old_point); } return point; } static void _adg_destroy(AdgEntity *entity) { GObject *object = (GObject *) entity; g_object_run_dispose(object); g_object_unref(object); } static void _adg_set_parent(AdgEntity *entity, AdgEntity *parent) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); AdgEntity *old_parent = data->parent; data->parent = parent; data->global.is_defined = FALSE; data->local.is_defined = FALSE; g_signal_emit(entity, _adg_signals[PARENT_SET], 0, old_parent); } static void _adg_global_changed(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); const cairo_matrix_t *map = &data->global_map; cairo_matrix_t *matrix = &data->global.matrix; if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_global_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } } static void _adg_local_changed(AdgEntity *entity) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); const cairo_matrix_t *map = &data->local_map; cairo_matrix_t *matrix = &data->local.matrix; switch (data->local_mix) { case ADG_MIX_DISABLED: adg_matrix_copy(matrix, adg_matrix_identity()); break; case ADG_MIX_NONE: adg_matrix_copy(matrix, map); break; case ADG_MIX_ANCESTORS: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } break; case ADG_MIX_ANCESTORS_NORMALIZED: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_matrix(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } adg_matrix_normalize(matrix); break; case ADG_MIX_PARENT: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } break; case ADG_MIX_PARENT_NORMALIZED: if (data->parent) { adg_matrix_copy(matrix, adg_entity_get_local_map(data->parent)); adg_matrix_transform(matrix, map, ADG_TRANSFORM_BEFORE); } else { adg_matrix_copy(matrix, map); } adg_matrix_normalize(matrix); break; case ADG_MIX_UNDEFINED: g_warning(_("%s: requested to mix the maps using an undefined mix method"), G_STRLOC); break; default: g_return_if_reached(); break; } } static void _adg_real_invalidate(AdgEntity *entity) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* Do not raise any warning if invalidate() is not defined, * assuming entity does not have additional cache to be cleared */ if (klass->invalidate) klass->invalidate(entity); data->extents.is_defined = FALSE; } static void _adg_real_arrange(AdgEntity *entity) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); AdgEntityPrivate *data = adg_entity_get_instance_private(entity); /* Update the global matrix, if required */ if (!data->global.is_defined) { data->global.is_defined = TRUE; g_signal_emit(entity, _adg_signals[GLOBAL_CHANGED], 0); } /* Update the local matrix, if required */ if (!data->local.is_defined) { data->local.is_defined = TRUE; g_signal_emit(entity, _adg_signals[LOCAL_CHANGED], 0); } /* The arrange() method must be defined */ if (klass->arrange == NULL) { g_warning(_("%s: 'arrange' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity))); data->extents.is_defined = FALSE; return; } klass->arrange(entity); } static void _adg_real_render(AdgEntity *entity, cairo_t *cr) { AdgEntityClass *klass = ADG_ENTITY_GET_CLASS(entity); /* The render method must be defined */ if (klass->render == NULL) { g_warning(_("%s: 'render' method not implemented for type '%s'"), G_STRLOC, g_type_name(G_OBJECT_TYPE(entity))); return; } /* Before the rendering, the entity should be arranged */ g_signal_emit(entity, _adg_signals[ARRANGE], 0); cairo_save(cr); klass->render(entity, cr); cairo_restore(cr); if (_adg_show_extents) { AdgEntityPrivate *data = adg_entity_get_instance_private(entity); CpmlExtents *extents = &data->extents; if (extents->is_defined) { cairo_save(cr); cairo_set_source_rgba(cr, 0.15, 0.15, 0.15, 0.15); cairo_rectangle(cr, extents->org.x, extents->org.y, extents->size.x, extents->size.y); cairo_fill(cr); cairo_restore(cr); } } } diff --git a/src/adg/adg-path.c b/src/adg/adg-path.c index 7d32ab5e..29fae89e 100644 --- a/src/adg/adg-path.c +++ b/src/adg/adg-path.c @@ -1,1592 +1,1592 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-path * @short_description: The basic model representing a generic path * * The #AdgPath model represents a virtual #cairo_path_t: this class * implements methods to create the path and provides additional * operations specific to technical drawings. * * #AdgPath overrides the get_cairo_path method * of the parent #AdgTrail class, avoiding the need of an * #AdgTrailCallback. The path is constructed programmatically: keep * in mind any method that modifies the path will invalidate the * #cairo_path_t returned by adg_trail_get_cairo_path(). * * Although some of the provided methods are clearly based on the * original cairo path manipulation API, their behavior could be * sligthly different. This is intentional, because the ADG provides * additional path manipulation algorithms, sometime quite complex, * and a more restrictive filter on the path quality is required. * Also, the ADG is designed to be used by technicians while cairo * targets a broader range of developers. * * As an example, following the rule of the less surprise, some * cairo functions guess the current point when it is not defined, * while the #AdgPath methods trigger a warning without other effect. * Furthermore, after cairo_close_path() a %CPML_MOVE primitive to * the starting point of the segment is automatically added by cairo; * in ADG, after an adg_path_close() the current point is unset. * * Since: 1.0 **/ /** * AdgPath: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-path.h" #include "adg-path-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_path_parent_class) #define _ADG_OLD_MODEL_CLASS ((AdgModelClass *) adg_path_parent_class) #define REMAPPED(ptr, from, to) \ (ptr) == NULL ? NULL : \ (gpointer) ((guint8 *) (ptr) - (guint8 *) (from) + (guint8 *) (to)) G_DEFINE_TYPE_WITH_PRIVATE(AdgPath, adg_path, ADG_TYPE_TRAIL) static void _adg_finalize (GObject *object); static void _adg_clear (AdgModel *model); static void _adg_clear_parent (AdgModel *model); static void _adg_changed (AdgModel *model); static cairo_path_t * _adg_get_cairo_path (AdgTrail *trail); static cairo_path_t * _adg_read_cairo_path (AdgPath *path); static gint _adg_primitive_length (CpmlPrimitiveType type); static void _adg_primitive_remap (CpmlPrimitive *primitive, gpointer to, const CpmlPrimitive *old, gconstpointer from); static void _adg_rescan (AdgPath *path); static void _adg_append_primitive (AdgPath *path, CpmlPrimitive *primitive); static void _adg_clear_operation (AdgPath *path); static gboolean _adg_append_operation (AdgPath *path, gint action, ...); static void _adg_do_operation (AdgPath *path, cairo_path_data_t *path_data); static void _adg_do_action (AdgPath *path, AdgAction action, CpmlPrimitive *primitive); static void _adg_do_chamfer (AdgPath *path, CpmlPrimitive *current); static void _adg_do_fillet (AdgPath *path, CpmlPrimitive *current); static gboolean _adg_is_convex (const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2); static const gchar * _adg_action_name (AdgAction action); static void _adg_get_named_pair (AdgModel *model, const gchar *name, CpmlPair *pair, gpointer user_data); static void _adg_dup_reverse_named_pairs (AdgModel *model, const cairo_matrix_t *matrix); static void adg_path_class_init(AdgPathClass *klass) { GObjectClass *gobject_class; AdgModelClass *model_class; AdgTrailClass *trail_class; gobject_class = (GObjectClass *) klass; model_class = (AdgModelClass *) klass; trail_class = (AdgTrailClass *) klass; gobject_class->finalize = _adg_finalize; model_class->clear = _adg_clear; model_class->changed = _adg_changed; trail_class->get_cairo_path = _adg_get_cairo_path; } static void adg_path_init(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); data->cp_is_valid = FALSE; data->cp.x = 0; data->cp.y = 0; data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = NULL; data->cairo.path.num_data = 0; data->cairo.array = g_array_new(FALSE, FALSE, sizeof(cairo_path_data_t)); data->last.segment = NULL; data->last.org = NULL; data->last.data = NULL; data->over.segment = NULL; data->over.org = NULL; data->over.data = NULL; data->operation.action = ADG_ACTION_NONE; } static void _adg_finalize(GObject *object) { AdgPath *path = (AdgPath *) object; AdgPathPrivate *data = adg_path_get_instance_private(path); g_array_free(data->cairo.array, TRUE); _adg_clear_operation(path); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } /** * adg_path_new: * * Creates a new path model. The path should be constructed * programmatically by using the methods provided by #AdgPath. * * Returns: the newly created path model * * Since: 1.0 **/ AdgPath * adg_path_new(void) { return g_object_new(ADG_TYPE_PATH, NULL); } /** * adg_path_get_current_point: * @path: an #AdgPath * * Gets the current point of @path, which is conceptually the * final point reached by the path so far. * * If there is no defined current point, NULL is returned. * It is possible to check this in advance with * adg_path_has_current_point(). * * Most #AdgPath methods alter the current point and most of them * expect a current point to be defined otherwise will fail triggering * a warning. Check the description of every method for specific details. * * Returns: (transfer none): the current point or NULL on no current point set or errors. * * Since: 1.0 **/ const CpmlPair * adg_path_get_current_point(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); if (!data->cp_is_valid) return NULL; return &data->cp; } /** * adg_path_has_current_point: * @path: an #AdgPath * * Returns whether a current point is defined on @path. * See adg_path_get_current_point() for details on the current point. * * Returns: whether a current point is defined * * Since: 1.0 **/ gboolean adg_path_has_current_point(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), FALSE); data = adg_path_get_instance_private(path); return data->cp_is_valid; } /** * adg_path_last_primitive: * @path: an #AdgPath * * Gets the last primitive appended to @path. The %CPML_MOVE type is * not considered a full-fledged primitive, i.e. adg_path_move_to() * or similar does not change the last primitive. * * The returned struct is owned by @path and should not be freed or * modified. * * Returns: (transfer none): a pointer to the last appended primitive or NULL on no last primitive or on errors. * * Since: 1.0 **/ const CpmlPrimitive * adg_path_last_primitive(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); /* Directly return NULL instead of returning an undefined primitive */ if (data->last.org == NULL || data->last.data == NULL) return NULL; return &data->last; } /** * adg_path_over_primitive: * @path: an #AdgPath * * Gets the primitive before the last one appended to @path. The * "over" term comes from forth, where the OVER * operator works on the stack in the same way as * adg_path_over_primitive() works on @path. The %CPML_MOVE type is * not considered a full-fledged primitive, i.e. adg_path_move_to() * or similar does not change the over primitive. * * The returned struct is owned by @path and should not be freed or * modified. * * Returns: (transfer none): a pointer to the primitive before the last appended one or NULL on errors. * * Since: 1.0 **/ const CpmlPrimitive * adg_path_over_primitive(AdgPath *path) { AdgPathPrivate *data; g_return_val_if_fail(ADG_IS_PATH(path), NULL); data = adg_path_get_instance_private(path); /* Directly return NULL instead of returning an undefined primitive */ if (data->over.org == NULL || data->over.data == NULL) return NULL; return &data->over; } /** * adg_path_append: * @path: an #AdgPath * @type: (type CpmlPrimitiveType): a #cairo_data_type_t value * @...: point data, specified as #CpmlPair pointers * * Generic method to append a primitive to @path. The number of #CpmlPair * pointers to pass as @Varargs depends on @type: %CPML_CLOSE does not * require any pair, %CPML_MOVE and %CPML_LINE require one pair, * %CPML_ARC two pairs, %CPML_CURVE three pairs and so on. * * All the needed pairs must be not NULL pointers, * otherwise the function will fail. The pairs in excess, if any, are ignored. * * Since: 1.0 **/ void adg_path_append(AdgPath *path, gint type, ...) { va_list var_args; va_start(var_args, type); adg_path_append_valist(path, (CpmlPrimitiveType) type, var_args); va_end(var_args); } /** * adg_path_append_valist: * @path: an #AdgPath * @type: a #cairo_data_type_t value * @var_args: point data, specified as #CpmlPair pointers * * va_list version of adg_path_append(). * * Since: 1.0 **/ void adg_path_append_valist(AdgPath *path, CpmlPrimitiveType type, va_list var_args) { GPtrArray *array; const CpmlPair *pair; gint length; length = _adg_primitive_length(type); if (length == 0) return; array = g_ptr_array_sized_new(4); while (-- length) { pair = va_arg(var_args, const CpmlPair *); if (pair == NULL) { g_ptr_array_free(array, TRUE); g_return_if_reached(); return; } g_ptr_array_add(array, (gpointer) pair); } /* The array must be NULL terminated */ g_ptr_array_add(array, NULL); adg_path_append_array(path, type, (const CpmlPair **) array->pdata); g_ptr_array_free(array, TRUE); } /** * adg_path_append_array: (rename-to adg_path_append) * @path: an #AdgPath * @type: a #cairo_data_type_t value * @pairs: (array zero-terminated=1) (element-type Cpml.Pair) (transfer none): point data, specified as a NULL terminated array of #CpmlPair pointers. * * A bindingable version of adg_path_append() that uses a * NULL terminated array of pairs instead of variable * argument list and friends. * * Furthermore, because of the list is NULL terminated, * an arbitrary number of pairs can be passed in @pairs. This allows to embed * in a primitive element more data pairs than requested, something impossible * to do with adg_path_append() and adg_path_append_valist(). * * Since: 1.0 **/ void adg_path_append_array(AdgPath *path, CpmlPrimitiveType type, const CpmlPair **pairs) { gint length; GArray *array; const CpmlPair **pair; cairo_path_data_t path_data; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(pairs != NULL); length = _adg_primitive_length(type); if (length == 0) return; array = g_array_new(FALSE, FALSE, sizeof(path_data)); for (pair = pairs; *pair != NULL; ++ pair) { cpml_pair_to_cairo(*pair, &path_data); g_array_append_val(array, path_data); } if (array->len < length - 1) { /* Not enough pairs have been provided */ g_warning(_("%s: null pair caught while parsing arguments"), G_STRLOC); } else { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive primitive; cairo_path_data_t org; /* Save a copy of the current point as primitive origin */ cpml_pair_to_cairo(&data->cp, &org); /* Prepend the cairo header */ path_data.header.type = type; path_data.header.length = array->len + 1; g_array_prepend_val(array, path_data); /* Append a new primitive to @path */ primitive.segment = NULL; primitive.org = &org; primitive.data = (cairo_path_data_t *) array->data; _adg_append_primitive(path, &primitive); } g_array_free(array, TRUE); } /** * adg_path_append_primitive: * @path: an #AdgPath * @primitive: the #CpmlPrimitive to append * * Appends @primitive to @path. The primitive to add is considered the * continuation of the current path so the org * component of @primitive is not used. Anyway the current point is * checked against it: they must be equal or the function will fail * without further processing. * * Since: 1.0 **/ void adg_path_append_primitive(AdgPath *path, const CpmlPrimitive *primitive) { AdgPathPrivate *data; CpmlPrimitive *primitive_dup; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(primitive != NULL); g_return_if_fail(primitive->org != NULL); g_return_if_fail(primitive->data != NULL); data = adg_path_get_instance_private(path); g_return_if_fail(primitive->org->point.x == data->cp.x && primitive->org->point.y == data->cp.y); /* The primitive data could be modified by pending operations: * work on a copy */ primitive_dup = cpml_primitive_deep_dup(primitive); _adg_append_primitive(path, primitive_dup); g_free(primitive_dup); } /** * adg_path_append_segment: * @path: an #AdgPath * @segment: the #CpmlSegment to append * * Appends @segment to @path. * * Since: 1.0 **/ void adg_path_append_segment(AdgPath *path, const CpmlSegment *segment) { g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(segment != NULL); if (segment->num_data > 0) { AdgPathPrivate *data; g_return_if_fail(segment->data != NULL); data = adg_path_get_instance_private(path); _adg_clear_parent((AdgModel *) path); data->cairo.array = g_array_append_vals(data->cairo.array, segment->data, segment->num_data); _adg_rescan(path); } } /** * adg_path_append_cairo_path: * @path: an #AdgPath * @cairo_path: the #cairo_path_t path to append * * Appends a whole #cairo_path_t to @path. * * Since: 1.0 **/ void adg_path_append_cairo_path(AdgPath *path, const cairo_path_t *cairo_path) { AdgPathPrivate *data; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(cairo_path != NULL); data = adg_path_get_instance_private(path); _adg_clear_parent((AdgModel *) path); data->cairo.array = g_array_append_vals(data->cairo.array, cairo_path->data, cairo_path->num_data); _adg_rescan(path); } /** * adg_path_append_trail: * @path: an #AdgPath * @trail: an #AdgTrail instance * * Appends the content of @trail to @path. It is similar to * adg_path_append_cairo_path() but it also appends to @path the named pairs * eventually defined in @trail. * * Since: 1.0 **/ void adg_path_append_trail(AdgPath *path, AdgTrail *trail) { GSList *named_pairs; AdgNamedPair *named_pair; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(ADG_IS_TRAIL(trail)); adg_path_append_cairo_path(path, adg_trail_get_cairo_path(trail)); /* Populate named_pairs with all the named pairs of trail */ named_pairs = NULL; adg_model_foreach_named_pair((AdgModel *)trail, _adg_get_named_pair, &named_pairs); /* Readd the pairs to path */ while (named_pairs) { named_pair = (AdgNamedPair *) named_pairs->data; adg_model_set_named_pair((AdgModel *) path, named_pair->name, &named_pair->pair); named_pairs = g_slist_delete_link(named_pairs, named_pairs); } } /** * adg_path_remove_primitive: * @path: an #AdgPath * * Removes the last primitive from @path. * * Since: 1.0 **/ void adg_path_remove_primitive(AdgPath *path) { AdgPathPrivate *data; const CpmlPrimitive *over; guint len; g_return_if_fail(ADG_IS_PATH(path)); data = adg_path_get_instance_private(path); over = adg_path_over_primitive(path); if (over) { cairo_path_data_t *end = over->data + over->data->header.length; len = end - (cairo_path_data_t *) (data->cairo.array)->data; } else { len = 0; } /* Resize the data array */ g_array_set_size(data->cairo.array, len); /* Rescan path to compute the new over and last primitives */ _adg_clear_parent((AdgModel *) path); _adg_rescan(path); } /** * adg_path_move_to: * @path: an #AdgPath * @pair: the destination coordinates * * Begins a new segment. After this call the current point will be @pair. * * Since: 1.0 **/ void adg_path_move_to(AdgPath *path, const CpmlPair *pair) { adg_path_append(path, CPML_MOVE, pair); } /** * adg_path_move_to_explicit: * @path: an #AdgPath * @x: the new x coordinate * @y: the new y coordinate * * Convenient function to call adg_path_move_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_move_to_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlPair p; p.x = x; p.y = y; adg_path_append(path, CPML_MOVE, &p); } /** * adg_path_line_to: * @path: an #AdgPath * @pair: the destination coordinates * * Adds a line to @path from the current point to @pair. After this * call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_line_to(AdgPath *path, const CpmlPair *pair) { adg_path_append(path, CPML_LINE, pair); } /** * adg_path_line_to_explicit: * @path: an #AdgPath * @x: the new x coordinate * @y: the new y coordinate * * Convenient function to call adg_path_line_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_line_to_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlPair p; p.x = x; p.y = y; adg_path_append(path, CPML_LINE, &p); } /** * adg_path_arc_to: * @path: an #AdgPath * @through: an arbitrary point on the arc * @pair: the destination coordinates * * Adds an arc to the path from the current point to @pair, passing * through @through. After this call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_arc_to(AdgPath *path, const CpmlPair *through, const CpmlPair *pair) { adg_path_append(path, CPML_ARC, through, pair); } /** * adg_path_arc_to_explicit: * @path: an #AdgPath * @x1: the x coordinate of an intermediate point * @y1: the y coordinate of an intermediate point * @x2: the x coordinate of the end of the arc * @y2: the y coordinate of the end of the arc * * Convenient function to call adg_path_arc_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_arc_to_explicit(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { CpmlPair p[2]; p[0].x = x1; p[0].y = y1; p[1].x = x2; p[1].y = y2; adg_path_append(path, CPML_ARC, &p[0], &p[1]); } /** * adg_path_curve_to: * @path: an #AdgPath * @control1: the first control point of the curve * @control2: the second control point of the curve * @pair: the destination coordinates * * Adds a cubic Bézier curve to the path from the current point to * position @pair, using @control1 and @control2 as control points. * After this call the current point will be @pair. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_curve_to(AdgPath *path, const CpmlPair *control1, const CpmlPair *control2, const CpmlPair *pair) { adg_path_append(path, CPML_CURVE, control1, control2, pair); } /** * adg_path_curve_to_explicit: * @path: an #AdgPath * @x1: the x coordinate of the first control point * @y1: the y coordinate of the first control point * @x2: the x coordinate of the second control point * @y2: the y coordinate of the second control point * @x3: the x coordinate of the end of the curve * @y3: the y coordinate of the end of the curve * * Convenient function to call adg_path_curve_to() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_curve_to_explicit(AdgPath *path, gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble x3, gdouble y3) { CpmlPair p[3]; p[0].x = x1; p[0].y = y1; p[1].x = x2; p[1].y = y2; p[2].x = x3; p[2].y = y3; adg_path_append(path, CPML_CURVE, &p[0], &p[1], &p[2]); } /** * adg_path_close: * @path: an #AdgPath * * Adds a line segment to the path from the current point to the * beginning of the current segment, (the most recent point passed * to an adg_path_move_to()), and closes this segment. * After this call the current point will be unset. * * The behavior of adg_path_close() is distinct from simply calling * adg_path_line_to() with the coordinates of the segment starting * point. When a closed segment is stroked, there are no caps on the * ends. Instead, there is a line join connecting the final and * initial primitive of the segment. * * If @path has no current point before this call, this function will * trigger a warning without other effect. * * Since: 1.0 **/ void adg_path_close(AdgPath *path) { adg_path_append(path, CPML_CLOSE); } /** * adg_path_arc: * @path: an #AdgPath * @center: coordinates of the center of the arc * @r: the radius of the arc * @start: the start angle, in radians * @end: the end angle, in radians * * A more usual way to add an arc to @path. After this call, the current * point will be the computed end point of the arc. The arc will be * rendered in increasing angle, accordling to @start and @end. This means * if @start is less than @end, the arc will be rendered in clockwise * direction (accordling to the default cairo coordinate system) while if * @start is greather than @end, the arc will be rendered in couterclockwise * direction. * - * By explicitely setting the whole arc data, the start point could be + * By explicitly setting the whole arc data, the start point could be * different from the current point. In this case, if @path has no * current point before the call a %CPML_MOVE to the start point of * the arc will be automatically prepended to the arc. If @path has a * current point, a %CPML_LINE to the start point of the arc will be * used instead of the "move to" primitive. * * Since: 1.0 **/ void adg_path_arc(AdgPath *path, const CpmlPair *center, gdouble r, gdouble start, gdouble end) { AdgPathPrivate *data; CpmlPair p[3]; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(center != NULL); data = adg_path_get_instance_private(path); cpml_vector_from_angle(&p[0], start); cpml_vector_from_angle(&p[1], (start+end) / 2); cpml_vector_from_angle(&p[2], end); cpml_vector_set_length(&p[0], r); cpml_vector_set_length(&p[1], r); cpml_vector_set_length(&p[2], r); p[0].x += center->x; p[0].y += center->y; p[1].x += center->x; p[1].y += center->y; p[2].x += center->x; p[2].y += center->y; if (!data->cp_is_valid) adg_path_append(path, CPML_MOVE, &p[0]); else if (p[0].x != data->cp.x || p[0].y != data->cp.y) adg_path_append(path, CPML_LINE, &p[0]); adg_path_append(path, CPML_ARC, &p[1], &p[2]); } /** * adg_path_arc_explicit: * @path: an #AdgPath * @xc: x position of the center of the arc * @yc: y position of the center of the arc * @r: the radius of the arc * @start: the start angle, in radians * @end: the end angle, in radians * * Convenient function to call adg_path_arc() using explicit * coordinates instead of #CpmlPair. * * Since: 1.0 **/ void adg_path_arc_explicit(AdgPath *path, gdouble xc, gdouble yc, gdouble r, gdouble start, gdouble end) { CpmlPair center; center.x = xc; center.y = yc; adg_path_arc(path, ¢er, r, start, end); } /** * adg_path_chamfer: * @path: an #AdgPath * @delta1: the distance from the intersection point of the current primitive * @delta2: the distance from the intersection point of the next primitive * * A binary action that generates a chamfer between two primitives. * The first primitive involved is the current primitive, the second will * be the next primitive appended to @path after this call. The second * primitive is required: if the chamfer operation is not properly * terminated (by not providing the second primitive), any API accessing * the path in reading mode will raise a warning. * * An exception is a chamfer after a %CPML_CLOSE primitive. In this case, * the second primitive is not required: the current close path is used * as first operand while the first primitive of the current segment is * used as second operand. * * The chamfer operation requires two lengths: @delta1 specifies the * "quantity" to trim on the first primitive while @delta2 is the same * applied on the second primitive. The term "quantity" means the length * of the portion to cut out from the original primitive (that is the * primitive as would be without the chamfer). * * Since: 1.0 **/ void adg_path_chamfer(AdgPath *path, gdouble delta1, gdouble delta2) { g_return_if_fail(ADG_IS_PATH(path)); if (!_adg_append_operation(path, ADG_ACTION_CHAMFER, delta1, delta2)) return; } /** * adg_path_fillet: * @path: an #AdgPath * @radius: the radius of the fillet * * A binary action that joins to primitives with an arc. * The first primitive involved is the current primitive, the second will * be the next primitive appended to @path after this call. The second * primitive is required: if the fillet operation is not properly * terminated (by not providing the second primitive), any API accessing * the path in reading mode will raise a warning. * * An exception is a fillet after a %CPML_CLOSE primitive. In this case, * the second primitive is not required: the current close path is used * as first operand while the first primitive of the current segment is * used as second operand. * * Since: 1.0 **/ void adg_path_fillet(AdgPath *path, gdouble radius) { g_return_if_fail(ADG_IS_PATH(path)); if (!_adg_append_operation(path, ADG_ACTION_FILLET, radius)) return; } /** * adg_path_join: * @path: an #AdgPath * * Joins all the segments of @path. After the call there will be only one * single segment. * * This operation is roughly equivalent to converting embedded %CPML_MOVE * primitives into %CPML_LINE ones. * * Since: 1.0 **/ void adg_path_join(AdgPath *path) { cairo_path_t *cairo_path; cairo_path_data_t *data; gboolean pen_down; g_return_if_fail(ADG_IS_PATH(path)); cairo_path = _adg_read_cairo_path(path); pen_down = FALSE; data = cairo_path->data; while (data - cairo_path->data < cairo_path->num_data) { if (data->header.type != CPML_MOVE) { pen_down = TRUE; } else if (pen_down) { data->header.type = CPML_LINE; } data += data->header.length; } } /** * adg_path_reflect: * @path: an #AdgPath * @vector: (allow-none): the slope of the axis * * Reflects the first segment or @path around the axis passing * through (0, 0) and with a @vector slope. The internal segment * is duplicated and the proper transformation (computed from * @vector) to mirror the segment is applied on all its points. * The result is then reversed with cpml_segment_reverse() and * appended to the original path with adg_path_append_segment(). * * For convenience, if @vector is NULL the * path is reversed around the x axis (y = 0). * * Since: 1.0 **/ void adg_path_reflect(AdgPath *path, const CpmlVector *vector) { AdgModel *model; AdgTrail *trail; cairo_matrix_t matrix; CpmlSegment segment, *dup_segment; gint n; g_return_if_fail(ADG_IS_PATH(path)); g_return_if_fail(vector == NULL || vector->x != 0 || vector->y != 0); model = (AdgModel *) path; trail = (AdgTrail *) path; if (vector == NULL) { cairo_matrix_init_scale(&matrix, 1, -1); } else { CpmlVector slope; gdouble cos2angle, sin2angle; cpml_pair_copy(&slope, vector); cpml_vector_set_length(&slope, 1); if (slope.x == 0 && slope.y == 0) { g_warning(_("%s: the axis of the reflection is not known"), G_STRLOC); return; } sin2angle = 2. * vector->x * vector->y; cos2angle = 2. * vector->x * vector->x - 1; cairo_matrix_init(&matrix, cos2angle, sin2angle, sin2angle, -cos2angle, 0, 0); } for (n = adg_trail_n_segments(trail); n > 0; --n) { adg_trail_put_segment(trail, n, &segment); /* No need to reverse an empty segment */ if (segment.num_data == 0 || segment.num_data == 0) continue; dup_segment = cpml_segment_deep_dup(&segment); if (dup_segment == NULL) return; cpml_segment_reverse(dup_segment); cpml_segment_transform(dup_segment, &matrix); dup_segment->data[0].header.type = CPML_MOVE; adg_path_append_segment(path, dup_segment); g_free(dup_segment); } _adg_dup_reverse_named_pairs(model, &matrix); } /** * adg_path_reflect_explicit: * @path: an #AdgPath * @x: the vector x component * @y: the vector y component * * Convenient function to call adg_path_reflect() using explicit * vector components instead of #CpmlVector. * * Since: 1.0 **/ void adg_path_reflect_explicit(AdgPath *path, gdouble x, gdouble y) { CpmlVector vector; vector.x = x; vector.y = y; adg_path_reflect(path, &vector); } static void _adg_clear(AdgModel *model) { AdgPath *path = (AdgPath *) model; AdgPathPrivate *data = adg_path_get_instance_private(path); g_array_set_size(data->cairo.array, 0); _adg_clear_operation(path); _adg_clear_parent(model); } static void _adg_clear_parent(AdgModel *model) { if (_ADG_OLD_MODEL_CLASS->clear) _ADG_OLD_MODEL_CLASS->clear(model); } static void _adg_changed(AdgModel *model) { _adg_clear_parent(model); if (_ADG_OLD_MODEL_CLASS->changed) _ADG_OLD_MODEL_CLASS->changed(model); } static cairo_path_t * _adg_get_cairo_path(AdgTrail *trail) { _adg_clear_parent((AdgModel *) trail); return _adg_read_cairo_path((AdgPath *) trail); } static cairo_path_t * _adg_read_cairo_path(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); cairo_path_t *cairo_path = &data->cairo.path; GArray *array = data->cairo.array; /* Always regenerate the cairo_path_t as it is a trivial operation */ cairo_path->status = CAIRO_STATUS_SUCCESS; cairo_path->data = (cairo_path_data_t *) array->data; cairo_path->num_data = array->len; return cairo_path; } static gint _adg_primitive_length(CpmlPrimitiveType type) { switch (type) { case CPML_CLOSE: return 1; case CPML_MOVE: return 2; default: return cpml_primitive_type_get_n_points(type); } } static void _adg_primitive_remap(CpmlPrimitive *primitive, gpointer to, const CpmlPrimitive *old, gconstpointer from) { primitive->org = REMAPPED(old->org, from, to); primitive->segment = REMAPPED(old->segment, from, to); primitive->data = REMAPPED(old->data, from, to); } static void _adg_rescan(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; CpmlPrimitive *over = &data->over; CpmlSegment segment; CpmlPrimitive current; last->segment = NULL; last->org = NULL; last->data = NULL; over->segment = NULL; over->org = NULL; over->data = NULL; /* When no data is present, just bail out */ if (! cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path))) { data->cp_is_valid = FALSE; return; } do { cpml_primitive_from_segment(¤t, &segment); do { cpml_primitive_copy(over, last); cpml_primitive_copy(last, ¤t); } while (cpml_primitive_next(¤t)); } while (cpml_segment_next(&segment)); /* Save the last point in the current point */ data->cp_is_valid = last->data && last->data->header.type != CPML_CLOSE; if (data->cp_is_valid) { CpmlPrimitiveType type = last->data->header.type; size_t n = type == CPML_MOVE ? 1 : cpml_primitive_type_get_n_points(type) - 1; cpml_pair_from_cairo(&data->cp, &last->data[n]); } } static void _adg_append_primitive(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); cairo_path_data_t *path_data = current->data; int length = path_data->header.length; CpmlPrimitiveType type = path_data->header.type; gconstpointer old_data; gpointer new_data; /* Execute any pending operation */ _adg_do_operation(path, path_data); /* Append the path data to the internal path array */ old_data = (data->cairo.array)->data; data->cairo.array = g_array_append_vals(data->cairo.array, path_data, length); new_data = (data->cairo.array)->data; /* Set path data to point to the recently appended cairo_path_data_t * primitive: the first struct is the header */ path_data = (cairo_path_data_t *) new_data + (data->cairo.array)->len - length; if (type == CPML_MOVE) { /* Remap last and over, but do not change their content */ _adg_primitive_remap(&data->last, new_data, &data->last, old_data); _adg_primitive_remap(&data->over, new_data, &data->over, old_data); } else { /* Store the last primitive into over */ _adg_primitive_remap(&data->over, new_data, &data->last, old_data); /* Set the last primitive for subsequent binary operations */ /* TODO: the assumption path_data - 1 is the last point is not true * e.g. when there are embedded data in primitives */ data->last.org = data->cp_is_valid ? path_data - 1 : NULL; data->last.segment = NULL; data->last.data = path_data; } data->cp_is_valid = type != CPML_CLOSE; if (data->cp_is_valid) { /* Save the last point in the current point */ size_t n = type == CPML_MOVE ? 1 : cpml_primitive_type_get_n_points(type) - 1; cpml_pair_from_cairo(&data->cp, &path_data[n]); } /* Invalidate cairo_path: should be recomputed */ _adg_clear_parent((AdgModel *) path); } static void _adg_clear_operation(AdgPath *path) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgOperation *operation = &data->operation; if (operation->action != ADG_ACTION_NONE) { g_warning(_("%s: a '%s' operation is still active while clearing the path"), G_STRLOC, _adg_action_name(operation->action)); operation->action = ADG_ACTION_NONE; } data->cp_is_valid = FALSE; data->last.data = NULL; data->over.data = NULL; } static gboolean _adg_append_operation(AdgPath *path, gint action, ...) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgAction real_action = (AdgAction) action; AdgOperation *operation; va_list var_args; if (data->last.data == NULL) { g_warning(_("%s: requested a '%s' operation on a path without current primitive"), G_STRLOC, _adg_action_name(real_action)); return FALSE; } operation = &data->operation; if (operation->action != ADG_ACTION_NONE) { g_warning(_("%s: requested a '%s' operation while a '%s' operation was active"), G_STRLOC, _adg_action_name(real_action), _adg_action_name(operation->action)); /* XXX: http://dev.entidi.com/p/adg/issues/50/ */ return FALSE; } va_start(var_args, action); switch (real_action) { case ADG_ACTION_CHAMFER: operation->data.chamfer.delta1 = va_arg(var_args, double); operation->data.chamfer.delta2 = va_arg(var_args, double); break; case ADG_ACTION_FILLET: operation->data.fillet.radius = va_arg(var_args, double); break; case ADG_ACTION_NONE: va_end(var_args); return TRUE; default: g_warning(_("%s: %d path operation not recognized"), G_STRLOC, real_action); va_end(var_args); return FALSE; } operation->action = real_action; va_end(var_args); if (data->last.data[0].header.type == CPML_CLOSE) { /* Special case: an action with the close primitive should * be resolved now by changing the close primitive to a * line-to and using it as second operand and use the first * primitive of the current segment as first operand */ guint length; cairo_path_data_t *path_data; CpmlSegment segment; CpmlPrimitive current; length = data->cairo.array->len; /* Ensure the close path primitive is not the only data */ g_return_val_if_fail(length > 1, FALSE); /* Allocate one more item once for all to accept the * conversion from a close to line-to primitive */ data->cairo.array = g_array_set_size(data->cairo.array, length + 1); path_data = (cairo_path_data_t *) data->cairo.array->data; --data->cairo.array->len; /* Set segment and current (the first primitive of segment) */ cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path)); while (cpml_segment_next(&segment)) ; cpml_primitive_from_segment(¤t, &segment); /* Convert close path to a line-to primitive */ ++data->cairo.array->len; path_data[length - 1].header.type = CPML_LINE; path_data[length - 1].header.length = 2; path_data[length] = *current.org; data->last.segment = &segment; data->last.org = &path_data[length - 2]; data->last.data = &path_data[length - 1]; _adg_do_action(path, real_action, ¤t); } return TRUE; } static void _adg_do_operation(AdgPath *path, cairo_path_data_t *path_data) { AdgPathPrivate *data = adg_path_get_instance_private(path); AdgAction action = data->operation.action; CpmlSegment segment; CpmlPrimitive current; cairo_path_data_t current_org; cpml_segment_from_cairo(&segment, _adg_read_cairo_path(path)); /* Construct the current primitive, that is the primitive to be * mixed with the last primitive with the specified operation. * Its org is a copy of the end point of the last primitive: it can be * modified without affecting anything else. It is expected the operation * functions will add to @path the primitives required but NOT to add * @current, as this one will be inserted automatically. */ current.segment = &segment; current.org = ¤t_org; current.data = path_data; cpml_pair_to_cairo(&data->cp, ¤t_org); _adg_do_action(path, action, ¤t); } static void _adg_do_action(AdgPath *path, AdgAction action, CpmlPrimitive *primitive) { switch (action) { case ADG_ACTION_NONE: return; case ADG_ACTION_CHAMFER: _adg_do_chamfer(path, primitive); break; case ADG_ACTION_FILLET: _adg_do_fillet(path, primitive); break; default: g_return_if_reached(); } } static void _adg_do_chamfer(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; gdouble delta1 = data->operation.data.chamfer.delta1; gdouble len1 = cpml_primitive_get_length(last); gdouble delta2, len2; CpmlPair pair; if (delta1 >= len1) { g_warning(_("%s: first chamfer delta of %lf is greather than the available %lf length"), G_STRLOC, delta1, len1); return; } delta2 = data->operation.data.chamfer.delta2; len2 = cpml_primitive_get_length(current); if (delta2 >= len2) { g_warning(_("%s: second chamfer delta of %lf is greather than the available %lf length"), G_STRLOC, delta1, len1); return; } /* Change the end point of the last primitive */ cpml_primitive_put_pair_at(last, 1. - delta1 / len1, &pair); cpml_primitive_set_point(last, -1, &pair); /* Change the start point of the current primitive */ cpml_primitive_put_pair_at(current, delta2 / len2, &pair); cpml_primitive_set_point(current, 0, &pair); /* Add the chamfer line */ data->operation.action = ADG_ACTION_NONE; adg_path_append(path, CPML_LINE, &pair); } static void _adg_do_fillet(AdgPath *path, CpmlPrimitive *current) { AdgPathPrivate *data = adg_path_get_instance_private(path); CpmlPrimitive *last = &data->last; CpmlPrimitive *current_dup = cpml_primitive_deep_dup(current); CpmlPrimitive *last_dup = cpml_primitive_deep_dup(last); gdouble radius = data->operation.data.fillet.radius; gdouble offset = _adg_is_convex(last_dup, current_dup) ? -radius : radius; gdouble pos; CpmlPair center, vector, p[3]; /* Find the center of the fillet from the intersection between * the last and current primitives offseted by radius */ cpml_primitive_offset(current_dup, offset); cpml_primitive_offset(last_dup, offset); if (cpml_primitive_put_intersections(current_dup, last_dup, 1, ¢er) == 0) { g_warning(_("%s: fillet with radius of %lf is not applicable here"), G_STRLOC, radius); g_free(current_dup); g_free(last_dup); return; } /* Compute the start point of the fillet */ pos = cpml_primitive_get_closest_pos(last_dup, ¢er); cpml_primitive_put_vector_at(last_dup, pos, &vector); cpml_vector_set_length(&vector, offset); cpml_vector_normal(&vector); p[0].x = center.x - vector.x; p[0].y = center.y - vector.y; /* Compute the mid point of the fillet */ cpml_pair_from_cairo(&vector, current->org); vector.x -= center.x; vector.y -= center.y; cpml_vector_set_length(&vector, radius); p[1].x = center.x + vector.x; p[1].y = center.y + vector.y; /* Compute the end point of the fillet */ pos = cpml_primitive_get_closest_pos(current_dup, ¢er); cpml_primitive_put_vector_at(current_dup, pos, &vector); cpml_vector_set_length(&vector, offset); cpml_vector_normal(&vector); p[2].x = center.x - vector.x; p[2].y = center.y - vector.y; g_free(current_dup); g_free(last_dup); /* Change the end point of the last primitive */ cpml_primitive_set_point(last, -1, &p[0]); /* Change the start point of the current primitive */ cpml_primitive_set_point(current, 0, &p[2]); /* Add the fillet arc */ data->operation.action = ADG_ACTION_NONE; adg_path_append(path, CPML_ARC, &p[1], &p[2]); } static gboolean _adg_is_convex(const CpmlPrimitive *primitive1, const CpmlPrimitive *primitive2) { CpmlVector v1, v2; gdouble angle1, angle2; cpml_primitive_put_vector_at(primitive1, -1, &v1); cpml_primitive_put_vector_at(primitive2, 0, &v2); /* Probably there is a smarter way to get this without trygonometry */ angle1 = cpml_vector_angle(&v1); angle2 = cpml_vector_angle(&v2); if (angle1 > angle2) angle1 -= G_PI*2; return angle2-angle1 > G_PI; } static const gchar * _adg_action_name(AdgAction action) { switch (action) { case ADG_ACTION_NONE: return "NULL"; case ADG_ACTION_CHAMFER: return "CHAMFER"; case ADG_ACTION_FILLET: return "FILLET"; } return "undefined"; } static void _adg_get_named_pair(AdgModel *model, const gchar *name, CpmlPair *pair, gpointer user_data) { GSList **named_pairs; AdgNamedPair *named_pair; named_pairs = user_data; named_pair = g_new(AdgNamedPair, 1); named_pair->name = name; named_pair->pair = *pair; *named_pairs = g_slist_prepend(*named_pairs, named_pair); } static void _adg_dup_reverse_named_pairs(AdgModel *model, const cairo_matrix_t *matrix) { AdgNamedPair *old_named_pair; AdgNamedPair named_pair; GSList *named_pairs; /* Populate named_pairs with all the named pairs of model */ named_pairs = NULL; adg_model_foreach_named_pair(model, _adg_get_named_pair, &named_pairs); /* Readd the pairs applying the reversing transformation matrix to * their coordinates and prepending a "-" to their name */ while (named_pairs) { old_named_pair = named_pairs->data; named_pair.name = g_strdup_printf("-%s", old_named_pair->name); named_pair.pair = old_named_pair->pair; cpml_pair_transform(&named_pair.pair, matrix); adg_model_set_named_pair(model, named_pair.name, &named_pair.pair); g_free((gpointer) named_pair.name); named_pairs = g_slist_delete_link(named_pairs, named_pairs); } } diff --git a/src/adg/adg-point.c b/src/adg/adg-point.c index 6d70d2d4..8ee39b45 100644 --- a/src/adg/adg-point.c +++ b/src/adg/adg-point.c @@ -1,454 +1,454 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-point * @Section_Id:AdgPoint * @title: AdgPoint * @short_description: A struct holding x, y coordinates * (either named or explicit) * * AdgPoint is an opaque structure that manages 2D coordinates, - * either set explicitely through adg_point_set_pair() and + * either set explicitly through adg_point_set_pair() and * adg_point_set_pair_explicit() or taken from a model with * adg_point_set_pair_from_model(). It can be thought as an * #CpmlPair on steroid, because it adds named pair support to * a simple pair, enabling coordinates depending on #AdgModel. * * Since: 1.0 **/ /** * AdgPoint: * * This is an opaque struct: all its fields are privates. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include #include "adg-point.h" struct _AdgPoint { CpmlPair pair; AdgModel *model; gchar *name; gboolean up_to_date; }; GType adg_point_get_type(void) { static GType type = 0; if (G_UNLIKELY(type == 0)) type = g_boxed_type_register_static("AdgPoint", (GBoxedCopyFunc) adg_point_dup, (GBoxedFreeFunc) adg_point_destroy); return type; } /** * adg_point_new: * * Creates a new empty #AdgPoint. The returned pointer * should be freed with adg_point_destroy() when no longer needed. * * Returns: a newly created #AdgPoint * * Since: 1.0 **/ AdgPoint * adg_point_new(void) { return g_new0(AdgPoint, 1); } /** * adg_point_dup: * @src: an #AdgPoint * * Duplicates @src. This operation also adds a new reference * to the internal model if @src is linked to a named pair. * * The returned value should be freed with adg_point_destroy() * when no longer needed. * * Returns: the duplicated #AdgPoint struct or NULL on errors. * * Since: 1.0 **/ AdgPoint * adg_point_dup(const AdgPoint *src) { AdgPoint *point; g_return_val_if_fail(src != NULL, NULL); if (src->model) g_object_ref(src->model); point = cpml_memdup(src, sizeof(AdgPoint)); point->name = g_strdup(src->name); return point; } /** * adg_point_destroy: * @point: an #AdgPoint * * Destroys the @point instance, unreferencing the internal model if * @point is linked to a named pair. * * Since: 1.0 **/ void adg_point_destroy(AdgPoint *point) { g_return_if_fail(point != NULL); adg_point_unset(point); g_free(point); } /** * adg_point_copy: * @point: an #AdgPoint * @src: the source point to copy * * Copies @src into @point. If the old content of @point was linked * to the named pair of a model, the reference to that model is * dropped. Similary, if @src is a named pair, a new reference to * the new model is added. * * Since: 1.0 **/ void adg_point_copy(AdgPoint *point, const AdgPoint *src) { g_return_if_fail(point != NULL); g_return_if_fail(src != NULL); if (src->model != NULL) g_object_ref(src->model); if (point->model != NULL) g_object_unref(point->model); if (point->name != NULL) g_free(point->name); memcpy(point, src, sizeof(AdgPoint)); point->name = g_strdup(src->name); } /** * adg_point_set_pair: * @point: an #AdgPoint * @pair: the #CpmlPair to use * * Sets an explicit pair in @point by using the given @pair. If * @point was linked to a named pair in a model, this link is * dropped before setting the pair. * * Since: 1.0 **/ void adg_point_set_pair(AdgPoint *point, const CpmlPair *pair) { g_return_if_fail(point != NULL); g_return_if_fail(pair != NULL); adg_point_set_pair_explicit(point, pair->x, pair->y); } /** * adg_point_set_pair_explicit: * @point: an #AdgPoint * @x: the x coordinate of the point * @y: the y coordinate of the point * * Works in the same way of adg_point_set_pair() but accept direct numbers * instead of an #CpmlPair structure. * * Since: 1.0 **/ void adg_point_set_pair_explicit(AdgPoint *point, gdouble x, gdouble y) { g_return_if_fail(point != NULL); adg_point_unset(point); point->pair.x = x; point->pair.y = y; point->up_to_date = TRUE; } /** * adg_point_set_pair_from_model: * @point: an #AdgPoint * @model: the #AdgModel * @name: the id of a named pair in @model * * Links the @name named pair of @model to @point, so any subsequent * call to adg_point_get_pair() will return the named pair value. * A new reference is added to @model while the previous model (if any) * is unreferenced. * * Since: 1.0 **/ void adg_point_set_pair_from_model(AdgPoint *point, AdgModel *model, const gchar *name) { g_return_if_fail(point != NULL); g_return_if_fail(ADG_IS_MODEL(model)); g_return_if_fail(name != NULL); /* Return if the new named pair is the same of the old one */ if (model == point->model && strcmp(point->name, name) == 0) return; g_object_ref(model); if (point->model) { /* Remove the old named pair */ g_object_unref(point->model); g_free(point->name); } /* Set the new named pair */ point->up_to_date = FALSE; point->model = model; point->name = g_strdup(name); } /** * adg_point_invalidate: * @point: an #AdgPoint * * Invalidates @point, forcing a refresh of its internal #CpmlPair if - * the point is linked to a named pair. If @point is explicitely set, + * the point is linked to a named pair. If @point is explicitly set, * this function has no effect. * * Since: 1.0 **/ void adg_point_invalidate(AdgPoint *point) { g_return_if_fail(point != NULL); if (point->model != NULL) point->up_to_date = FALSE; } /** * adg_point_unset: * @point: a pointer to an #AdgPoint * * Unsets @point by resetting the internal up_to_date * flag and (eventually) unlinking it from the named pair it is bound * to. After this call the content of @point is undefined, so a * subsequent call to adg_point_get_pair() will * return NULL raising a warning. * * Since: 1.0 **/ void adg_point_unset(AdgPoint *point) { g_return_if_fail(point != NULL); if (point->model) { /* Remove the old named pair */ g_object_unref(point->model); g_free(point->name); } point->up_to_date = FALSE; point->model = NULL; point->name = NULL; } /** * adg_point_update: * @point: a pointer to an #AdgPoint * * Updates the internal #CpmlPair of @point. The internal * implementation is protected against multiple calls so it * can be called more times without harms. * * Returns: TRUE if @point has been updated or FALSE on errors, i.e. when it is bound to a non-existent named pair. * * Since: 1.0 **/ gboolean adg_point_update(AdgPoint *point) { AdgModel *model; const CpmlPair *pair; g_return_val_if_fail(point != NULL, FALSE); if (point->up_to_date) return TRUE; model = point->model; if (model == NULL) { /* A point with explicit coordinates not up to date * is an unexpected condition */ g_warning(_("%s: trying to get a pair from an undefined point"), G_STRLOC); return FALSE; } pair = adg_model_get_named_pair(model, point->name); if (pair == NULL) return FALSE; cpml_pair_copy(&point->pair, pair); point->up_to_date = TRUE; return TRUE; } /** * adg_point_get_pair: * @point: an #AdgPoint * * #AdgPoint is an evolution of the pair concept but internally the * relevant data is still stored in an #CpmlPair struct. This function * returns a copy of the internally owned pair. * * * The #CpmlPair is the first field of an #AdgPoint struct so casting * is allowed between them and, in fact, it is often more convenient * than calling this function. Just remember to update the internal * pair by using adg_point_update() before. * * * Returns: (transfer full): the pair of @point or NULL if the named pair does not exist. * * Since: 1.0 **/ CpmlPair * adg_point_get_pair(AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); if (! adg_point_update(point)) return NULL; return cpml_pair_dup(& point->pair); } /** * adg_point_get_model: * @point: an #AdgPoint * * Gets the source model of the named pair bound to @point, or * returns NULL if @point is an explicit * pair. The returned value is owned by @point. * * Returns: (transfer none): an #AdgModel or NULL. * * Since: 1.0 **/ AdgModel * adg_point_get_model(const AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); return point->model; } /** * adg_point_get_name: * @point: an #AdgPoint * * Gets the name of the named pair bound to @point, or * returns NULL if @point is an explicit * pair. The returned value is owned by @point and should not * be modified or freed. * * Returns: the name of the named pair or NULL. * * Since: 1.0 **/ const gchar * adg_point_get_name(const AdgPoint *point) { g_return_val_if_fail(point != NULL, NULL); return point->name; } /** * adg_point_equal: * @point1: the first point to compare * @point2: the second point to compare * * Compares @point1 and @point2 and returns TRUE * if the points are equals. The comparison is made by checking also * the named pairs they are bound to. If you want to compare only * their coordinates, use cpml_pair_equal() directly on the * #AdgPoint structs: * * * if (adg_point_update(point1) && * adg_point_update(point2) && * cpml_pair_equal((CpmlPair *) point1, (CpmlPair *) point2)) * { * ... * } * * * NULL points are handled gracefully. * * Returns: TRUE if @point1 is equal to @point2, FALSE otherwise. * * Since: 1.0 **/ gboolean adg_point_equal(const AdgPoint *point1, const AdgPoint *point2) { if (point1 == point2) return TRUE; if (point1 == NULL || point2 == NULL) return FALSE; /* Check if the points are not bound to the same model * or if only one of the two points is explicit */ if (point1->model != point2->model) return FALSE; /* Handle points bound to named pairs */ if (point1->model != NULL) return g_strcmp0(point1->name, point2->name) == 0; /* Handle points with explicit coordinates */ return cpml_pair_equal(&point1->pair, &point2->pair); } diff --git a/src/adg/adg-rdim.c b/src/adg/adg-rdim.c index 3157fd4c..1bb5eef5 100644 --- a/src/adg/adg-rdim.c +++ b/src/adg/adg-rdim.c @@ -1,616 +1,616 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-rdim * @short_description: Radial dimensions * * The #AdgRDim entity represents a radial dimension. * * Since: 1.0 **/ /** * AdgRDim: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-model.h" #include "adg-point.h" #include "adg-trail.h" #include "adg-marker.h" #include "adg-style.h" #include "adg-dim-style.h" #include "adg-dress.h" #include "adg-textual.h" #include "adg-toy-text.h" #include "adg-dim.h" #include "adg-dim-private.h" #include #include "adg-rdim.h" #include "adg-rdim-private.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_rdim_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_rdim_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgRDim, adg_rdim, ADG_TYPE_DIM) enum { PROP_0, }; static void _adg_dispose (GObject *object); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_render (AdgEntity *entity, cairo_t *cr); static gchar * _adg_default_value (AdgDim *dim); static gboolean _adg_compute_geometry (AdgDim *dim); static void _adg_update_entities (AdgRDim *rdim); static void _adg_clear_trail (AdgRDim *rdim); static void _adg_dispose_trail (AdgRDim *rdim); static void _adg_dispose_marker (AdgRDim *rdim); static cairo_path_t * _adg_trail_callback (AdgTrail *trail, gpointer user_data); static void adg_rdim_class_init(AdgRDimClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; AdgDimClass *dim_class; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; dim_class = (AdgDimClass *) klass; gobject_class->dispose = _adg_dispose; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; dim_class->default_value = _adg_default_value; dim_class->compute_geometry = _adg_compute_geometry; } static void adg_rdim_init(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); AdgStyle *style; AdgDimStyle *dim_style; cairo_path_data_t move_to, line_to; move_to.header.type = CPML_MOVE; move_to.header.length = 2; line_to.header.type = CPML_LINE; line_to.header.length = 2; data->trail = NULL; data->marker = NULL; data->radius = -1.; data->angle = 0.; data->shift.base.x = data->shift.base.y = 0; cairo_matrix_init_identity(&data->quote.global_map); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; data->cairo.path.data = data->cairo.data; data->cairo.path.num_data = G_N_ELEMENTS(data->cairo.data); data->cairo.path.data[0] = move_to; data->cairo.path.data[2] = line_to; data->cairo.path.data[4] = move_to; data->cairo.path.data[6] = line_to; /* Override the default dimension style to prefix the quote with an R */ style = adg_dress_get_fallback(ADG_DRESS_DIMENSION); dim_style = (AdgDimStyle *) adg_style_clone(style); adg_dim_style_set_number_format(dim_style, "R%g"); adg_entity_set_style((AdgEntity *) rdim, ADG_DRESS_DIMENSION, (AdgStyle *) dim_style); } static void _adg_dispose(GObject *object) { AdgRDim *rdim = (AdgRDim *) object; _adg_dispose_trail(rdim); _adg_dispose_marker(rdim); if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } /** * adg_rdim_new: * * Creates a new uninitialized radial dimension. To be useful, you * need at least define the center of the arc to quote in #AdgDim:ref1, * a point on the arc in #AdgDim:ref2 and the position of the quote * in #AdgDim:pos using any valid #AdgDim method. * * Returns: a newly created quote * * Since: 1.0 **/ AdgRDim * adg_rdim_new(void) { return g_object_new(ADG_TYPE_RDIM, NULL); } /** * adg_rdim_new_full: * @center: (allow-none): center of the arc to quote * @radius: (allow-none): where the quote must be applied on the arc * @pos: (allow-none): position of the quote text * - * Creates a new quote by specifying explicitely all the needed + * Creates a new quote by specifying explicitly all the needed * data to get a valid quote. * * Returns: the newly created quote. * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full(const CpmlPair *center, const CpmlPair *radius, const CpmlPair *pos) { AdgRDim *rdim; AdgDim *dim; rdim = adg_rdim_new(); dim = (AdgDim *) rdim; if (center != NULL) adg_dim_set_ref1_from_pair(dim, center); if (radius != NULL) adg_dim_set_ref2_from_pair(dim, radius); if (pos != NULL) adg_dim_set_pos_from_pair(dim, pos); return rdim; } /** * adg_rdim_new_full_explicit: * @center_x: x coordinate of the center of the arc to quote * @center_y: y coordinate of the center of the arc to quote * @radius_x: x coordiante where the quote must be applied on the arc * @radius_y: y coordiante where the quote must be applied on the arc * @pos_x: x coordinate of the quote text * @pos_y: y coordinate of the quote text * * Does the same job of adg_rdim_new_full() but using specific * coordinates instead of #CpmlPair structures. * * Returns: the newly created quote. * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full_explicit(gdouble center_x, gdouble center_y, gdouble radius_x, gdouble radius_y, gdouble pos_x, gdouble pos_y) { CpmlPair center, radius, pos; center.x = center_x; center.y = center_y; radius.x = radius_x; radius.y = radius_y; pos.x = pos_x; pos.y = pos_y; return adg_rdim_new_full(¢er, &radius, &pos); } /** * adg_rdim_new_full_from_model: * @model: (transfer none): the model from which the named pairs are taken * @center: (allow-none): the center point of the arc to quote * @radius: (allow-none): an arbitrary point on the arc * @pos: (allow-none): the position reference * * Creates a new radial dimension, specifing all the needed properties in * one shot and using named pairs from @model. * * Returns: the newly created radial dimension entity * * Since: 1.0 **/ AdgRDim * adg_rdim_new_full_from_model(AdgModel *model, const gchar *center, const gchar *radius, const gchar *pos) { AdgRDim *rdim; AdgDim *dim; g_return_val_if_fail(model != NULL, NULL); rdim = adg_rdim_new(); dim = (AdgDim *) rdim; if (center != NULL) adg_dim_set_ref1_from_model(dim, model, center); if (radius != NULL) adg_dim_set_ref2_from_model(dim, model, radius); if (pos != NULL) adg_dim_set_pos_from_model(dim, model, pos); return rdim; } static void _adg_global_changed(AdgEntity *entity) { AdgRDim *rdim = (AdgRDim *) entity; AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); _adg_clear_trail(rdim); if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); if (data->marker != NULL) adg_entity_global_changed((AdgEntity *) data->marker); } static void _adg_local_changed(AdgEntity *entity) { _adg_clear_trail((AdgRDim *) entity); if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); } static void _adg_invalidate(AdgEntity *entity) { AdgRDim *rdim = (AdgRDim *) entity; _adg_dispose_trail(rdim); _adg_dispose_marker(rdim); _adg_clear_trail(rdim); if (_ADG_OLD_ENTITY_CLASS->invalidate) _ADG_OLD_ENTITY_CLASS->invalidate(entity); } static void _adg_arrange(AdgEntity *entity) { AdgRDim *rdim; AdgDim *dim; AdgRDimPrivate *data; AdgAlignment *quote; AdgEntity *quote_entity; gboolean outside; const cairo_matrix_t *global, *local; CpmlPair ref2, base; CpmlPair pair; CpmlExtents extents; if (_ADG_OLD_ENTITY_CLASS->arrange != NULL) _ADG_OLD_ENTITY_CLASS->arrange(entity); dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) return; rdim = (AdgRDim *) entity; data = adg_rdim_get_instance_private(rdim); quote = adg_dim_get_quote(dim); quote_entity = (AdgEntity *) quote; _adg_update_entities(rdim); /* Check for cached result */ if (data->cairo.path.status == CAIRO_STATUS_SUCCESS) { adg_entity_set_global_map(quote_entity, &data->quote.global_map); return; } outside = adg_dim_get_outside(dim); if (outside == ADG_THREE_STATE_UNKNOWN) outside = ADG_THREE_STATE_OFF; global = adg_entity_get_global_matrix(entity); local = adg_entity_get_local_matrix(entity); extents.is_defined = FALSE; cpml_pair_copy(&ref2, (CpmlPair *) adg_dim_get_ref2(dim)); cpml_pair_copy(&base, &data->point.base); cpml_pair_transform(&ref2, local); cpml_pair_transform(&base, local); base.x += data->shift.base.x; base.y += data->shift.base.y; /* baseline start */ cpml_pair_to_cairo(&base, &data->cairo.data[1]); /* baseline end */ cpml_pair_to_cairo(&ref2, &data->cairo.data[3]); if (outside) { AdgDimStyle *dim_style; gdouble beyond; CpmlVector vector; dim_style = adg_dim_get_dim_style(dim); beyond = adg_dim_style_get_beyond(dim_style); vector.x = ref2.x - base.x; vector.y = ref2.y - base.y; cpml_vector_set_length(&vector, beyond); pair.x = ref2.x + vector.x; pair.y = ref2.y + vector.y; data->cairo.data[2].header.length = 2; /* Outside segment start */ cpml_pair_to_cairo(&pair, &data->cairo.data[5]); /* Outside segment end */ cpml_pair_to_cairo(&ref2, &data->cairo.data[7]); } else { data->cairo.data[2].header.length = 6; } /* Arrange the quote */ if (quote != NULL) { cairo_matrix_t map; gdouble x_align; gdouble quote_angle = adg_dim_quote_angle(dim, data->angle); x_align = cpml_angle_distance(quote_angle, data->angle) > G_PI_2 ? 0 : 1; adg_alignment_set_factor_explicit(quote, x_align, 0); cpml_pair_from_cairo(&pair, &data->cairo.data[1]); cairo_matrix_init_translate(&map, pair.x, pair.y); cairo_matrix_rotate(&map, quote_angle); adg_entity_set_global_map(quote_entity, &map); adg_entity_arrange(quote_entity); cpml_extents_add(&extents, adg_entity_get_extents(quote_entity)); adg_matrix_copy(&data->quote.global_map, &map); } data->cairo.path.status = CAIRO_STATUS_SUCCESS; /* Arrange the trail */ if (data->trail != NULL) { CpmlExtents trail_extents; cpml_extents_copy(&trail_extents, adg_trail_get_extents(data->trail)); cpml_extents_transform(&trail_extents, global); cpml_extents_add(&extents, &trail_extents); } else { _adg_dispose_marker(rdim); } /* Arrange the marker */ if (data->marker != NULL) { AdgEntity *marker_entity = (AdgEntity *) data->marker; adg_marker_set_segment(data->marker, data->trail, outside ? 2 : 1); adg_entity_local_changed(marker_entity); adg_entity_arrange(marker_entity); cpml_extents_add(&extents, adg_entity_get_extents(marker_entity)); } adg_entity_set_extents(entity, &extents); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgDim *dim; AdgRDimPrivate *data; AdgDimStyle *dim_style; AdgDress dress; const cairo_path_t *cairo_path; dim = (AdgDim *) entity; if (! adg_dim_compute_geometry(dim)) { /* Entity not arranged, probably due to undefined pair found */ return; } data = adg_rdim_get_instance_private((AdgRDim *) dim); dim_style = adg_dim_get_dim_style(dim); adg_style_apply((AdgStyle *) dim_style, entity, cr); adg_entity_render((AdgEntity *) adg_dim_get_quote(dim), cr); if (data->marker != NULL) adg_entity_render((AdgEntity *) data->marker, cr); cairo_transform(cr, adg_entity_get_global_matrix(entity)); dress = adg_dim_style_get_line_dress(dim_style); adg_entity_apply_dress(entity, dress, cr); cairo_path = adg_trail_get_cairo_path(data->trail); cairo_append_path(cr, cairo_path); cairo_stroke(cr); } static gchar * _adg_default_value(AdgDim *dim) { AdgRDimPrivate *data; if (! adg_dim_compute_geometry(dim)) return g_strdup("undef"); data = adg_rdim_get_instance_private((AdgRDim *) dim); return adg_dim_get_text(dim, data->radius); } static gboolean _adg_compute_geometry(AdgDim *dim) { AdgRDimPrivate *data; AdgDimStyle *dim_style; AdgPoint *ref1_point, *ref2_point, *pos_point; const CpmlPair *ref1, *ref2, *pos; gdouble spacing, level, pos_distance; CpmlVector vector; ref1_point = adg_dim_get_ref1(dim); if (! adg_point_update(ref1_point)) { adg_dim_geometry_missing(dim, "ref1"); return FALSE; } ref2_point = adg_dim_get_ref2(dim); if (! adg_point_update(ref2_point)) { adg_dim_geometry_missing(dim, "ref2"); return FALSE; } pos_point = adg_dim_get_pos(dim); if (! adg_point_update(pos_point)) { adg_dim_geometry_missing(dim, "pos"); return FALSE; } ref1 = (CpmlPair *) ref1_point; ref2 = (CpmlPair *) ref2_point; if (cpml_pair_equal(ref1, ref2)) { adg_dim_geometry_coincident(dim, "ref1", "ref2", ref1); return FALSE; } data = adg_rdim_get_instance_private((AdgRDim *) dim); pos = (CpmlPair *) pos_point; dim_style = adg_dim_get_dim_style(dim); spacing = adg_dim_style_get_baseline_spacing(dim_style); level = adg_dim_get_level(dim); pos_distance = cpml_pair_distance(pos, ref1); vector.x = ref2->x - ref1->x; vector.y = ref2->y - ref1->y; if (cpml_pair_squared_distance(pos, ref1) < cpml_pair_squared_distance(pos, ref2)) { vector.x = -vector.x; vector.y = -vector.y; } /* radius */ data->radius = cpml_pair_distance(&vector, NULL); /* angle */ data->angle = cpml_vector_angle(&vector); /* point.base */ cpml_pair_copy(&data->point.base, &vector); cpml_vector_set_length(&data->point.base, pos_distance); data->point.base.x += ref1->x; data->point.base.y += ref1->y; /* shift.base */ cpml_pair_copy(&data->shift.base, &vector); cpml_vector_set_length(&data->shift.base, spacing * level); return TRUE; } static void _adg_update_entities(AdgRDim *rdim) { AdgEntity *entity = (AdgEntity *) rdim; AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); AdgDimStyle *dim_style = adg_dim_get_dim_style((AdgDim *) rdim); if (data->trail == NULL) data->trail = adg_trail_new(_adg_trail_callback, rdim); if (data->marker == NULL) { data->marker = adg_dim_style_marker2_new(dim_style); adg_entity_set_parent((AdgEntity *) data->marker, entity); } } static void _adg_clear_trail(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->trail != NULL) adg_model_clear((AdgModel *) data->trail); data->cairo.path.status = CAIRO_STATUS_INVALID_PATH_DATA; } static void _adg_dispose_trail(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->trail != NULL) { g_object_unref(data->trail); data->trail = NULL; } } static void _adg_dispose_marker(AdgRDim *rdim) { AdgRDimPrivate *data = adg_rdim_get_instance_private(rdim); if (data->marker != NULL) { g_object_unref(data->marker); data->marker = NULL; } } static cairo_path_t * _adg_trail_callback(AdgTrail *trail, gpointer user_data) { AdgRDimPrivate *data = adg_rdim_get_instance_private((AdgRDim *) user_data); return &data->cairo.path; } diff --git a/src/adg/adg-table-style.c b/src/adg/adg-table-style.c index b75339d2..b7fabdb1 100644 --- a/src/adg/adg-table-style.c +++ b/src/adg/adg-table-style.c @@ -1,592 +1,592 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table-style * @short_description: Customization of table rendering * * Contains parameters on how to build tables such as the lines to * * Since: 1.0 */ /** * AdgTableStyle: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-style.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-table-style.h" #include "adg-table-style-private.h" G_DEFINE_TYPE_WITH_PRIVATE(AdgTableStyle, adg_table_style, ADG_TYPE_STYLE) enum { PROP_0, PROP_COLOR_DRESS, PROP_GRID_DRESS, PROP_FRAME_DRESS, PROP_TITLE_DRESS, PROP_VALUE_DRESS, PROP_ROW_HEIGHT, PROP_CELL_PADDING, PROP_CELL_SPACING }; static void _adg_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void _adg_apply (AdgStyle *style, AdgEntity *entity, cairo_t *cr); static void adg_table_style_class_init(AdgTableStyleClass *klass) { GObjectClass *gobject_class; AdgStyleClass *style_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; style_class = (AdgStyleClass *) klass; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; style_class->apply = _adg_apply; param = adg_param_spec_dress("color-dress", P_("Color Dress"), P_("Fallback color dress, used when no specific dresses are selected"), ADG_DRESS_COLOR_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_COLOR_DRESS, param); param = adg_param_spec_dress("grid-dress", P_("Grid Dress"), P_("Line dress to use while rendering the grid of the table"), ADG_DRESS_LINE_GRID, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_GRID_DRESS, param); param = adg_param_spec_dress("frame-dress", P_("Frame Dress"), P_("Line dress to use while drawing the table frame"), ADG_DRESS_LINE_FRAME, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_FRAME_DRESS, param); param = adg_param_spec_dress("title-dress", P_("Title Dress"), P_("Font dress to use for titles"), ADG_DRESS_FONT_ANNOTATION, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TITLE_DRESS, param); param = adg_param_spec_dress("value-dress", P_("Value Dress"), P_("Font dress to use for values inside the cells"), ADG_DRESS_FONT_TEXT, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_VALUE_DRESS, param); param = g_param_spec_double("row-height", P_("Row Height"), - P_("The fallback row height when not explicitely specified while creating a new row"), + P_("The fallback row height when not explicitly specified while creating a new row"), 0, G_MAXDOUBLE, 30, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_ROW_HEIGHT, param); param = g_param_spec_boxed("cell-padding", P_("Cell Padding"), P_("How much space from the bounding box must left inside every cell"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CELL_PADDING, param); param = g_param_spec_boxed("cell-spacing", P_("Cell Spacing"), P_("How much space to left between the cells"), CPML_TYPE_PAIR, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_CELL_SPACING, param); } static void adg_table_style_init(AdgTableStyle *table_style) { AdgTableStylePrivate *data = adg_table_style_get_instance_private(table_style); data->color_dress = ADG_DRESS_COLOR_ANNOTATION, data->grid_dress = ADG_DRESS_LINE_GRID; data->frame_dress = ADG_DRESS_LINE_FRAME; data->title_dress = ADG_DRESS_FONT_ANNOTATION; data->value_dress = ADG_DRESS_FONT_TEXT; data->row_height = 30; data->cell_padding.x = 3; data->cell_padding.y = 3; data->cell_spacing.x = 0; data->cell_spacing.y = 0; table_style->data = data; } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgTableStylePrivate *data = ((AdgTableStyle *) object)->data; switch (prop_id) { case PROP_COLOR_DRESS: g_value_set_enum(value, data->color_dress); break; case PROP_GRID_DRESS: g_value_set_enum(value, data->grid_dress); break; case PROP_FRAME_DRESS: g_value_set_enum(value, data->frame_dress); break; case PROP_TITLE_DRESS: g_value_set_enum(value, data->title_dress); break; case PROP_VALUE_DRESS: g_value_set_enum(value, data->value_dress); break; case PROP_ROW_HEIGHT: g_value_set_double(value, data->row_height); break; case PROP_CELL_PADDING: g_value_set_boxed(value, &data->cell_padding); break; case PROP_CELL_SPACING: g_value_set_boxed(value, &data->cell_spacing); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgTableStylePrivate *data = ((AdgTableStyle *) object)->data; switch (prop_id) { case PROP_COLOR_DRESS: data->color_dress = g_value_get_enum(value); break; case PROP_GRID_DRESS: data->grid_dress = g_value_get_enum(value); break; case PROP_FRAME_DRESS: data->frame_dress = g_value_get_enum(value); break; case PROP_TITLE_DRESS: data->title_dress = g_value_get_enum(value); break; case PROP_VALUE_DRESS: data->value_dress = g_value_get_enum(value); break; case PROP_ROW_HEIGHT: data->row_height = g_value_get_double(value); break; case PROP_CELL_PADDING: cpml_pair_copy(&data->cell_padding, g_value_get_boxed(value)); break; case PROP_CELL_SPACING: cpml_pair_copy(&data->cell_spacing, g_value_get_boxed(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_table_style_new: * * Constructs a new empty table style initialized with default params. * * Returns: (transfer full): a new table style. * * Since: 1.0 **/ AdgTableStyle * adg_table_style_new(void) { return g_object_new(ADG_TYPE_TABLE_STYLE, NULL); } /** * adg_table_style_set_color_dress: * @table_style: an #AdgTableStyle object * @dress: the new color dress * * Sets a new color dress on @table_style. * * Since: 1.0 **/ void adg_table_style_set_color_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "color-dress", dress, NULL); } /** * adg_table_style_get_color_dress: * @table_style: an #AdgTableStyle object * * Gets the @table_style color dress to be used. This dress should be * intended as a fallback color as it could be overriden by more - * specific dresses, such as a color explicitely specified on the + * specific dresses, such as a color explicitly specified on the * #AdgTableStyle:value-dress. * * Returns: (transfer none): the color dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_color_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->color_dress; } /** * adg_table_style_set_frame_dress: * @table_style: an #AdgTableStyle object * @dress: the new line dress * * Sets a new line dress on @table_style for rendering the frames. * * Since: 1.0 **/ void adg_table_style_set_frame_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "frame-dress", dress, NULL); } /** * adg_table_style_get_frame_dress: * @table_style: an #AdgTableStyle object * * Gets the line dress to be used for rendering the frames with * @table_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_frame_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->frame_dress; } /** * adg_table_style_set_grid_dress: * @table_style: an #AdgTableStyle object * @dress: the new line dress * * Sets a new line dress on @table_style for rendering the grids. * * Since: 1.0 **/ void adg_table_style_set_grid_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "grid-dress", dress, NULL); } /** * adg_table_style_get_grid_dress: * @table_style: an #AdgTableStyle object * * Gets the line dress to be used for rendering the grids with * @table_style. * * Returns: (transfer none): the line dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_grid_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->grid_dress; } /** * adg_table_style_set_title_dress: * @table_style: an #AdgTableStyle object * @dress: the new font dress * * Sets a new font dress on @table_style for rendering cell titles. * * Since: 1.0 **/ void adg_table_style_set_title_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "title-dress", dress, NULL); } /** * adg_table_style_get_title_dress: * @table_style: an #AdgTableStyle object * * Gets the font dress to be used for rendering cell titles * with @table_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_title_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->title_dress; } /** * adg_table_style_set_value_dress: * @table_style: an #AdgTableStyle object * @dress: the new font dress * * Sets a new font dress on @table_style for rendering cell values. * * Since: 1.0 **/ void adg_table_style_set_value_dress(AdgTableStyle *table_style, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "value-dress", dress, NULL); } /** * adg_table_style_get_value_dress: * @table_style: an #AdgTableStyle object * * Gets the font dress to be used for rendering cell values * with @table_style. * * Returns: (transfer none): the font dress. * * Since: 1.0 **/ AdgDress adg_table_style_get_value_dress(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), ADG_DRESS_UNDEFINED); data = table_style->data; return data->value_dress; } /** * adg_table_style_set_row_height: * @table_style: an #AdgTableStyle object * @height: the new row heigth fallback * * Sets a new #AdgTableStyle:row-height fallback. @height must * be a valid row height greather than 0 or a warning will be * raised and this function will fail. * * Since: 1.0 **/ void adg_table_style_set_row_height(AdgTableStyle *table_style, gdouble height) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "row-height", height, NULL); } /** * adg_table_style_get_row_height: * @table_style: an #AdgTableStyle object * * Gets the row height fallback value. * * Returns: the fallback row height or 0 on errors. * * Since: 1.0 **/ gdouble adg_table_style_get_row_height(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), 0); data = table_style->data; return data->row_height; } /** * adg_table_style_set_cell_padding: * @table_style: an #AdgTableStyle object * @padding: the new padding values * * Sets new #AdgTableStyle:cell-padding values. * * Since: 1.0 **/ void adg_table_style_set_cell_padding(AdgTableStyle *table_style, const CpmlPair *padding) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "cell-padding", padding, NULL); } /** * adg_table_style_get_cell_padding: * @table_style: an #AdgTableStyle object * * Gets the padding values in x and y to be left clear inside the cells. * The returned pointer refers to an internal allocated struct and * must not be modified or freed. * * The cell padding is a symmetric value, that is the padding on the * left will always be equal to the padding on the right and the top * will always be equal to the bottom. * * Returns: (transfer none): the cell padding values or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_table_style_get_cell_padding(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), NULL); data = table_style->data; return &data->cell_padding; } /** * adg_table_style_set_cell_spacing: * @table_style: an #AdgTableStyle object * @spacing: the new spacing values * * Sets new #AdgTableStyle:cell-spacing values. * * Since: 1.0 **/ void adg_table_style_set_cell_spacing(AdgTableStyle *table_style, const CpmlPair *spacing) { g_return_if_fail(ADG_IS_TABLE_STYLE(table_style)); g_object_set(table_style, "cell-spacing", spacing, NULL); } /** * adg_table_style_get_cell_spacing: * @table_style: an #AdgTableStyle object * * Gets the spacing values in x and y to be left between the cell * boundary boxes. The returned pointer refers to an internal * allocated struct and must not be modified or freed. * * The cell spacing is a symmetric value, that is the spacing on the * left will always be equal to the spacing on the right and the top * will always be equal to the bottom. * * Returns: (transfer none): the cell spacing values or NULL on errors. * * Since: 1.0 **/ const CpmlPair * adg_table_style_get_cell_spacing(AdgTableStyle *table_style) { AdgTableStylePrivate *data; g_return_val_if_fail(ADG_IS_TABLE_STYLE(table_style), NULL); data = table_style->data; return &data->cell_spacing; } static void _adg_apply(AdgStyle *style, AdgEntity *entity, cairo_t *cr) { AdgTableStylePrivate *data = ((AdgTableStyle *) style)->data; adg_entity_apply_dress(entity, data->color_dress, cr); } diff --git a/src/adg/adg-table.c b/src/adg/adg-table.c index 299f85a3..863c635a 100644 --- a/src/adg/adg-table.c +++ b/src/adg/adg-table.c @@ -1,827 +1,827 @@ /* ADG - Automatic Drawing Generation * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:adg-table * @short_description: A tabular entity * * The #AdgTable is the entity to be used for rendering data arranged * in tabular evironments. * * To define a table, you should add to it a serie of one or more * #AdgTableRow by using the #AdgTableRow specific APIs. * * * By default, the #AdgEntity:local-mix property is set to * #ADG_MIX_DISABLED on #AdgTable entities. * * * Since: 1.0 **/ /** * AdgTable: * * All fields are private and should not be used directly. * Use its public methods instead. * * Since: 1.0 **/ #include "adg-internal.h" #include "adg-model.h" #include "adg-trail.h" #include "adg-style.h" #include "adg-table-style.h" #include "adg-path.h" #include "adg-stroke.h" #include "adg-container.h" #include "adg-alignment.h" #include "adg-dress.h" #include "adg-param-dress.h" #include "adg-table.h" #include "adg-table-private.h" #include "adg-table-row.h" #include "adg-table-cell.h" #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class) #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class) G_DEFINE_TYPE_WITH_PRIVATE(AdgTable, adg_table, ADG_TYPE_ENTITY) enum { PROP_0, PROP_TABLE_DRESS, PROP_HAS_FRAME }; typedef struct { GCallback callback; gpointer user_data; } AdgClosure; static void _adg_dispose (GObject *object); static void _adg_finalize (GObject *object); static void _adg_get_property (GObject *object, guint param_id, GValue *value, GParamSpec *pspec); static void _adg_set_property (GObject *object, guint param_id, const GValue *value, GParamSpec *pspec); static void _adg_destroy (AdgEntity *entity); static void _adg_global_changed (AdgEntity *entity); static void _adg_local_changed (AdgEntity *entity); static void _adg_invalidate (AdgEntity *entity); static void _adg_arrange (AdgEntity *entity); static void _adg_arrange_grid (AdgEntity *entity); static void _adg_arrange_frame (AdgEntity *entity, const CpmlExtents *extents); static void _adg_render (AdgEntity *entity, cairo_t *cr); static void _adg_propagate (AdgTable *table, const gchar *detailed_signal, ...); static void _adg_foreach_row (AdgTableRow *table_row, const AdgClosure *closure); static void _adg_append_frame (AdgTableCell *table_cell, AdgPath *path); static void _adg_proxy_signal (AdgTableCell *table_cell, AdgProxyData *proxy_data); static gboolean _adg_value_match (gpointer key, gpointer value, gpointer user_data); static void adg_table_class_init(AdgTableClass *klass) { GObjectClass *gobject_class; AdgEntityClass *entity_class; GParamSpec *param; gobject_class = (GObjectClass *) klass; entity_class = (AdgEntityClass *) klass; gobject_class->dispose = _adg_dispose; gobject_class->finalize = _adg_finalize; gobject_class->get_property = _adg_get_property; gobject_class->set_property = _adg_set_property; entity_class->destroy = _adg_destroy; entity_class->global_changed = _adg_global_changed; entity_class->local_changed = _adg_local_changed; entity_class->invalidate = _adg_invalidate; entity_class->arrange = _adg_arrange; entity_class->render = _adg_render; param = adg_param_spec_dress("table-dress", P_("Table Dress"), P_("The dress to use for stroking this entity"), ADG_DRESS_TABLE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param); param = g_param_spec_boolean("has-frame", P_("Has Frame Flag"), P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"), TRUE, G_PARAM_READWRITE); g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param); } static void adg_table_init(AdgTable *table) { AdgTablePrivate *data = adg_table_get_instance_private(table); data->table_dress = ADG_DRESS_TABLE; data->has_frame = TRUE; data->table_style = NULL; data->grid = NULL; data->frame = NULL; data->rows = NULL; data->cell_names = NULL; adg_entity_set_local_mix((AdgEntity *) table, ADG_MIX_DISABLED); } static void _adg_dispose(GObject *object) { AdgTable *table = (AdgTable *) object; AdgTablePrivate *data = adg_table_get_instance_private(table); adg_table_invalidate_grid(table); if (data->frame) { g_object_unref(data->frame); data->frame = NULL; } if (data->rows) { adg_table_foreach_cell(table, (GCallback) adg_table_cell_dispose, NULL); data->rows = NULL; } if (_ADG_OLD_OBJECT_CLASS->dispose) _ADG_OLD_OBJECT_CLASS->dispose(object); } static void _adg_finalize(GObject *object) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) object); if (data->rows) { g_slist_foreach(data->rows, (GFunc) adg_table_row_free, NULL); g_slist_free(data->rows); } if (data->cell_names) g_hash_table_destroy(data->cell_names); if (_ADG_OLD_OBJECT_CLASS->finalize) _ADG_OLD_OBJECT_CLASS->finalize(object); } static void _adg_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) object); switch (prop_id) { case PROP_TABLE_DRESS: g_value_set_enum(value, data->table_dress); break; case PROP_HAS_FRAME: g_value_set_boolean(value, data->has_frame); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void _adg_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) object); switch (prop_id) { case PROP_TABLE_DRESS: data->table_dress = g_value_get_enum(value); break; case PROP_HAS_FRAME: data->has_frame = g_value_get_boolean(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } /** * adg_table_new: * * Creates a new empty table entity. * * Returns: the newly created table entity * * Since: 1.0 **/ AdgTable * adg_table_new(void) { return g_object_new(ADG_TYPE_TABLE, NULL); } /** * adg_table_insert: * @table: an #AdgTable * @table_row: a valid #AdgTableRow * @before_row: (allow-none): an #AdgTableRow or NULL * * Inserts @table_row inside the rows list of @table. If @before_row * is specified, @table_row is inserted before it. * * Since: 1.0 **/ void adg_table_insert(AdgTable *table, AdgTableRow *table_row, AdgTableRow *before_row) { AdgTablePrivate *data; g_return_if_fail(ADG_IS_TABLE(table)); g_return_if_fail(table_row != NULL); data = adg_table_get_instance_private(table); if (before_row == NULL) { data->rows = g_slist_append(data->rows, table_row); } else { GSList *before = g_slist_find(data->rows, before_row); /* This MUST be present, otherwise something really bad happened */ g_return_if_fail(before != NULL); data->rows = g_slist_insert_before(data->rows, before, table_row); } } /** * adg_table_remove: * @table: an #AdgTable * @table_row: a valid #AdgTableRow * * Removes @table_row from list of rows of @table. * * Since: 1.0 **/ void adg_table_remove(AdgTable *table, AdgTableRow *table_row) { AdgTablePrivate *data; g_return_if_fail(ADG_IS_TABLE(table)); g_return_if_fail(table_row != NULL); data = adg_table_get_instance_private(table); data->rows = g_slist_remove(data->rows, table_row); } /** * adg_table_foreach: * @table: an #AdgTable * @callback: (scope call): a callback * @user_data: callback user data * * Invokes @callback on each row of @table. * The callback should be declared as: * * * void callback(AdgTableRow *table_row, gpointer user_data); * * * Since: 1.0 **/ void adg_table_foreach(AdgTable *table, GCallback callback, gpointer user_data) { AdgTablePrivate *data; g_return_if_fail(table != NULL); g_return_if_fail(callback != NULL); data = adg_table_get_instance_private(table); g_slist_foreach(data->rows, (GFunc) callback, user_data); } /** * adg_table_foreach_cell: * @table: an #AdgTable * @callback: (scope call): a callback * @user_data: callback user data * * Invokes @callback on each cell of @table. * The callback should be declared as: * * * void callback(AdgTableCell *table_cell, gpointer user_data); * * * Since: 1.0 **/ void adg_table_foreach_cell(AdgTable *table, GCallback callback, gpointer user_data) { AdgClosure closure = { callback, user_data }; adg_table_foreach(table, (GCallback) _adg_foreach_row, &closure); } /** * adg_table_set_cell: * @table: an #AdgTable * @name: (nullable): the name of the cell * @table_cell: (transfer none) (nullable): the named cell * * Binds @table_cell to @name, so it can be accessed by name later * with adg_table_get_cell(). Internally the binding is handled with * an hash table, so accessing the cell this way is O(1). * * If @name is NULL, any binding to @table_cell * will be removed. This is quite inefficient because the whole hash * table must be scanned. * * If @table_cell is NULL, the key with @name * in the hash table will be removed. * * Both @name and @table_cell cannot be NULL at the same time. * * Since: 1.0 **/ void adg_table_set_cell(AdgTable *table, const gchar *name, AdgTableCell *table_cell) { AdgTablePrivate *data; g_return_if_fail(ADG_IS_TABLE(table)); g_return_if_fail(name != NULL || table_cell != NULL); data = adg_table_get_instance_private(table); if (data->cell_names == NULL) { /* Check if trying to remove from an empty hash table */ if (name == NULL || table_cell == NULL) return; data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); } if (name == NULL) { g_hash_table_foreach_remove(data->cell_names, _adg_value_match, table_cell); } else if (table_cell == NULL) { g_hash_table_remove(data->cell_names, name); } else { g_hash_table_insert(data->cell_names, g_strdup(name), table_cell); } } /** * adg_table_get_table_style: * @table: an #AdgTable * - * Gets the #AdgTableStyle explicitely set on @table. This is a kind + * Gets the #AdgTableStyle explicitly set on @table. This is a kind * of accessor function: for rendering purpose use adg_entity_style() * instead. The returned object is owned by @table and should not be * freed or modified. * * Returns: (transfer none): the requested style or NULL on errors. * * Since: 1.0 **/ AdgStyle * adg_table_get_table_style(AdgTable *table) { AdgTablePrivate *data; g_return_val_if_fail(ADG_IS_TABLE(table), NULL); data = adg_table_get_instance_private(table); return (AdgStyle *) data->table_style; } /** * adg_table_get_cell: * @table: an #AdgTable * @name: the name of a cell * * Gets the cell named @name inside @table. Only named cells * can be retrieved by this method. * * The returned cell is owned by @table and must not be * modified or freed. * * Returns: (transfer none): the requested cell or NULL if not found. * * Since: 1.0 **/ AdgTableCell * adg_table_get_cell(AdgTable *table, const gchar *name) { AdgTablePrivate *data; g_return_val_if_fail(ADG_IS_TABLE(table), NULL); data = adg_table_get_instance_private(table); if (data->cell_names == NULL) return NULL; return g_hash_table_lookup(data->cell_names, name); } /** * adg_table_set_table_dress: * @table: an #AdgTable * @dress: the new #AdgDress to use * * Sets a new table dress for rendering @table. The new dress * must be related to the original dress for this property: * you cannot set a dress used for line styles to a dress * managing fonts. * * The check is done by calling adg_dress_are_related() with * @dress and the previous dress as arguments. Check out its * documentation for details on what is a related dress. * * Since: 1.0 **/ void adg_table_set_table_dress(AdgTable *table, AdgDress dress) { g_return_if_fail(ADG_IS_TABLE(table)); g_object_set(table, "table-dress", dress, NULL); } /** * adg_table_get_table_dress: * @table: an #AdgTable * * Gets the table dress to be used in rendering @table. * * Returns: (transfer none): the current table dress. * * Since: 1.0 **/ AdgDress adg_table_get_table_dress(AdgTable *table) { AdgTablePrivate *data; g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED); data = adg_table_get_instance_private(table); return data->table_dress; } /** * adg_table_switch_frame: * @table: an #AdgTable * @new_state: the new state of the frame * * Sets the #AdgTable:has-frame property: TRUE * will draw a frame around the whole table using the * #AdgTableStyle:frame-dress dress of the table style. * * Since: 1.0 **/ void adg_table_switch_frame(AdgTable *table, gboolean new_state) { g_return_if_fail(ADG_IS_TABLE(table)); g_object_set(table, "has-frame", new_state, NULL); } /** * adg_table_has_frame: * @table: an #AdgTable * * Returns the state of the #AdgTable:has-frame property. * * Returns: the current state. * * Since: 1.0 **/ gboolean adg_table_has_frame(AdgTable *table) { AdgTablePrivate *data; g_return_val_if_fail(ADG_IS_TABLE(table), FALSE); data = adg_table_get_instance_private(table); return data->has_frame; } /** * adg_table_invalidate_grid: * @table: an #AdgTable * * * This method is only useful in table children implementation. * * * Clears the internal grid cache, effectively forcing its * regeneration next time the #AdgEntity::arrange signal is emitted. **/ void adg_table_invalidate_grid(AdgTable *table) { AdgTablePrivate *data; g_return_if_fail(ADG_IS_TABLE(table)); data = adg_table_get_instance_private(table); if (data->grid) { g_object_unref(data->grid); data->grid = NULL; } } static void _adg_destroy(AdgEntity *entity) { _adg_propagate((AdgTable *) entity, "destroy"); if (_ADG_OLD_ENTITY_CLASS->destroy) _ADG_OLD_ENTITY_CLASS->destroy(entity); } static void _adg_global_changed(AdgEntity *entity) { if (_ADG_OLD_ENTITY_CLASS->global_changed) _ADG_OLD_ENTITY_CLASS->global_changed(entity); _adg_propagate((AdgTable *) entity, "global-changed"); } static void _adg_local_changed(AdgEntity *entity) { if (_ADG_OLD_ENTITY_CLASS->local_changed) _ADG_OLD_ENTITY_CLASS->local_changed(entity); _adg_propagate((AdgTable *) entity, "local-changed"); } static void _adg_invalidate(AdgEntity *entity) { _adg_propagate((AdgTable *) entity, "invalidate"); } static void _adg_arrange(AdgEntity *entity) { AdgTable *table = (AdgTable *) entity; AdgTablePrivate *data = adg_table_get_instance_private(table); CpmlExtents extents = { 0 }; CpmlExtents row_layout; const CpmlExtents *row_extents; const CpmlPair *spacing; const CpmlPair *size; GSList *row_node; AdgTableRow *row; /* Resolve the table style */ if (data->table_style == NULL) data->table_style = (AdgTableStyle *) adg_entity_style(entity, data->table_dress); spacing = adg_table_style_get_cell_spacing(data->table_style); /* Compute the size of the table */ for (row_node = data->rows; row_node; row_node = row_node->next) { row = row_node->data; size = adg_table_row_size_request(row); if (size->x > extents.size.x) extents.size.x = size->x; extents.size.y += size->y; } /* Arrange the layout of the table components */ row_layout.is_defined = 1; row_layout.org.x = extents.org.x; row_layout.org.y = extents.org.y + spacing->y; row_layout.size.x = extents.size.x; row_layout.size.y = -1; for (row_node = data->rows; row_node; row_node = row_node->next) { row = row_node->data; row_extents = adg_table_row_arrange(row, &row_layout); row_layout.org.y += row_extents->size.y + spacing->y; } _adg_arrange_grid(entity); _adg_arrange_frame(entity, &extents); extents.is_defined = TRUE; cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity)); cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity)); adg_entity_set_extents(entity, &extents); } static void _adg_arrange_grid(AdgEntity *entity) { AdgTable *table = (AdgTable *) entity; AdgTablePrivate *data = adg_table_get_instance_private(table); AdgPath *path; AdgTrail *trail; AdgDress dress; if (data->grid) return; path = adg_path_new(); trail = (AdgTrail *) path; adg_table_foreach_cell(table, (GCallback) _adg_append_frame, path); if (!adg_trail_get_extents(trail)->is_defined) return; dress = adg_table_style_get_grid_dress(data->table_style); data->grid = g_object_new(ADG_TYPE_STROKE, "line-dress", dress, "trail", trail, "parent", entity, NULL); adg_entity_arrange((AdgEntity *) data->grid); } static void _adg_arrange_frame(AdgEntity *entity, const CpmlExtents *extents) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) entity); AdgPath *path; AdgTrail *trail; CpmlPair pair; AdgDress dress; if (data->frame || !data->has_frame) return; path = adg_path_new(); trail = (AdgTrail *) path; cpml_pair_copy(&pair, &extents->org); adg_path_move_to(path, &pair); pair.x += extents->size.x; adg_path_line_to(path, &pair); pair.y += extents->size.y; adg_path_line_to(path, &pair); pair.x -= extents->size.x; adg_path_line_to(path, &pair); adg_path_close(path); dress = adg_table_style_get_frame_dress(data->table_style); data->frame = g_object_new(ADG_TYPE_STROKE, "line-dress", dress, "trail", trail, "parent", entity, NULL); adg_entity_arrange((AdgEntity *) data->frame); } static void _adg_render(AdgEntity *entity, cairo_t *cr) { AdgTablePrivate *data = adg_table_get_instance_private((AdgTable *) entity); adg_style_apply((AdgStyle *) data->table_style, entity, cr); _adg_propagate((AdgTable *) entity, "render", cr); } static void _adg_propagate(AdgTable *table, const gchar *detailed_signal, ...) { va_list var_copy; AdgTablePrivate *data; AdgProxyData proxy_data; if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table), &proxy_data.signal_id, &proxy_data.detail, FALSE)) { g_return_if_reached(); } va_start(proxy_data.var_args, detailed_signal); data = adg_table_get_instance_private(table); if (data->frame) { G_VA_COPY(var_copy, proxy_data.var_args); g_signal_emit_valist(data->frame, proxy_data.signal_id, proxy_data.detail, var_copy); } if (data->grid) { G_VA_COPY(var_copy, proxy_data.var_args); g_signal_emit_valist(data->grid, proxy_data.signal_id, proxy_data.detail, var_copy); } adg_table_foreach_cell(table, (GCallback) _adg_proxy_signal, &proxy_data); va_end(proxy_data.var_args); } static void _adg_foreach_row(AdgTableRow *table_row, const AdgClosure *closure) { adg_table_row_foreach(table_row, closure->callback, closure->user_data); } static void _adg_append_frame(AdgTableCell *table_cell, AdgPath *path) { CpmlPair pair; const CpmlExtents *extents; if (! adg_table_cell_has_frame(table_cell)) return; extents = adg_table_cell_get_extents(table_cell); cpml_pair_copy(&pair, &extents->org); adg_path_move_to(path, &pair); pair.x += extents->size.x; adg_path_line_to(path, &pair); pair.y += extents->size.y; adg_path_line_to(path, &pair); pair.x -= extents->size.x; adg_path_line_to(path, &pair); adg_path_close(path); } static void _adg_proxy_signal(AdgTableCell *table_cell, AdgProxyData *proxy_data) { AdgEntity *entity; AdgAlignment *alignment; va_list var_copy; entity = adg_table_cell_title(table_cell); if (entity) { alignment = (AdgAlignment *) adg_entity_get_parent(entity); G_VA_COPY(var_copy, proxy_data->var_args); g_signal_emit_valist(alignment, proxy_data->signal_id, proxy_data->detail, var_copy); } entity = adg_table_cell_value(table_cell); if (entity) { alignment = (AdgAlignment *) adg_entity_get_parent(entity); G_VA_COPY(var_copy, proxy_data->var_args); g_signal_emit_valist(alignment, proxy_data->signal_id, proxy_data->detail, var_copy); } } static gboolean _adg_value_match(gpointer key, gpointer value, gpointer user_data) { return value == user_data; } diff --git a/src/cpml/cpml-arc.c b/src/cpml/cpml-arc.c index f6920ef0..04f0eeb1 100644 --- a/src/cpml/cpml-arc.c +++ b/src/cpml/cpml-arc.c @@ -1,617 +1,617 @@ /* CPML - Cairo Path Manipulation Library * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:cpml-arc * @Section_Id:CpmlArc * @title: CpmlArc * @short_description: Manipulation of circular arcs * * The following functions manipulate %CPML_ARC #CpmlPrimitive. * No validation is made on the input so use the following methods * only when you are sure the primitive argument * is effectively an arc-to. * * The arc primitive is defined by 3 points: the first one is the usual * implicit point got from the previous primitive, the second point is * an arbitrary intermediate point laying on the arc and the third point * is the end of the arc. These points identify univocally an arc: * furthermore, the intermediate point also gives the side of * the arc. * * As a special case, when the first point is coincident with the end * point the primitive is considered a circle with diameter defined by * the segment between the first and the intermediate point. * * * * An arc is not a native cairo primitive and should be treated specially. * * * * Using these CPML APIs you are free to use %CPML_ARC whenever * you want but, if you are directly accessing the struct fields, you * are responsible of converting arcs to curves before passing them * to cairo. In other words, do not directly feed #cairo_data_path_t * got from CPML to cairo (i.e. by using cairo_append_path()) or at * least do not expect it will work if an arc is present. * * The conversion is provided by two APIs: cpml_arc_to_cairo() and * cpml_arc_to_curves(). The former directly renders to a cairo context * and is internally used by all the ..._to_cairo() functions when an * arc is met. The latter provided a more powerful (and more complex) * approach as it allows to specify the number of curves to use and do * not need a cairo context. * * * TODO * * the get_closest_pos method must be * implemented; * the put_intersections method must * implement arc-arc intersections. * * * * Since: 1.0 **/ #include "cpml-internal.h" #include "cpml-extents.h" #include "cpml-segment.h" #include "cpml-primitive.h" #include "cpml-primitive-private.h" #include "cpml-arc.h" #include "cpml-curve.h" #include /* Hardcoded max angle of the arc to be approximated by a Bézier curve: * this influence the arc quality (the default value is got from cairo) */ #define ARC_MAX_ANGLE M_PI_2 /* Macro to save typing and make put_extents() code cleaner */ #define ANGLE_INCLUDED(d) \ ((start < (d) && end > (d)) || (start > (d) && end < (d))) static double get_length (const CpmlPrimitive *arc); static void put_extents (const CpmlPrimitive *arc, CpmlExtents *extents); static void put_pair_at (const CpmlPrimitive *arc, double pos, CpmlPair *pair); static void put_vector_at (const CpmlPrimitive *arc, double pos, CpmlVector *vector); static size_t put_intersections (const CpmlPrimitive *line, const CpmlPrimitive *primitive, size_t n_dest, CpmlPair *dest); static void offset (CpmlPrimitive *arc, double offset); static int get_center (const CpmlPair *p, CpmlPair *dest); static void get_angles (const CpmlPair *p, const CpmlPair *center, double *start, double *end); static void arc_to_curve (CpmlPrimitive *curve, const CpmlPair *center, double r, double start, double end); static int circle_line (const CpmlPair *center, double r, const CpmlPair *p1, const CpmlPair *p2, CpmlPair *dest); const _CpmlPrimitiveClass * _cpml_arc_get_class(void) { static _CpmlPrimitiveClass *p_class = NULL; if (p_class == NULL) { static _CpmlPrimitiveClass class_data = { "arc to", 3, get_length, put_extents, put_pair_at, put_vector_at, NULL, put_intersections, offset, NULL }; p_class = &class_data; } return p_class; } /** * cpml_arc_info: * @arc: (in): the #CpmlPrimitive arc data * @center: (out) (allow-none): where to store the center coordinates * @r: (out) (allow-none): where to store the radius * @start: (out) (allow-none): where to store the starting angle * @end: (out) (allow-none): where to store the ending angle * * Given an @arc, this function calculates and returns its basic data. * Any pointer can be NULL, in which case the requested * info is not returned. This function can fail (when the three points lay on * a straight line, for example) in which case 0 is returned and no data can * be considered valid. * * The radius @r can be 0 when the three points are coincidents: a * circle with radius 0 is considered a valid path. * * When the start and end angle are returned, together with their * values these angles implicitely gives another important information: * the arc direction. * * If @start < @end the arc must be rendered with increasing angle * value (clockwise direction using the ordinary cairo coordinate * system) while if @start > @end the arc must be rendered in reverse * order (that is counterclockwise in the cairo world). This is the * reason the angle values are returned in the range -M_PI * < value < 3 M_PI inclusive instead of * the usual -M_PI < value < M_PI. * * Returns: (type boolean): 1 if the function worked succesfully, 0 on errors. * * Since: 1.0 **/ int cpml_arc_info(const CpmlPrimitive *arc, CpmlPair *center, double *r, double *start, double *end) { CpmlPair p[3], l_center; cpml_pair_from_cairo(&p[0], arc->org); cpml_pair_from_cairo(&p[1], &arc->data[1]); cpml_pair_from_cairo(&p[2], &arc->data[2]); if (! get_center(p, &l_center)) return 0; if (center) *center = l_center; if (r != NULL) *r = cpml_pair_distance(&p[0], &l_center); if (start != NULL || end != NULL) { double l_start, l_end; get_angles(p, &l_center, &l_start, &l_end); if (start != NULL) *start = l_start; if (end != NULL) *end = l_end; } return 1; } /** * cpml_arc_to_cairo: * @arc: (in): the #CpmlPrimitive arc data * @cr: (inout): the destination cairo context * * Renders @arc to the @cr cairo context. As cairo does not support * arcs natively, it is approximated using one or more Bézier curves. * * The number of curves used is dependent from the angle of the arc. * Anyway, this function uses internally the hardcoded M_PI_2 value * as threshold value. This means the maximum arc approximated by a * single curve will be a quarter of a circle and, consequently, a * whole circle will be approximated by 4 Bézier curves. * * Since: 1.0 **/ void cpml_arc_to_cairo(const CpmlPrimitive *arc, cairo_t *cr) { CpmlPair center; double r, start, end; size_t n_curves; double step, angle; CpmlPrimitive curve; cairo_path_data_t data[4]; if (!cpml_arc_info(arc, ¢er, &r, &start, &end)) return; n_curves = ceil(fabs(end-start) / ARC_MAX_ANGLE); step = (end-start) / (double) n_curves; curve.data = data; for (angle = start; n_curves--; angle += step) { arc_to_curve(&curve, ¢er, r, angle, angle+step); cairo_curve_to(cr, curve.data[1].point.x, curve.data[1].point.y, curve.data[2].point.x, curve.data[2].point.y, curve.data[3].point.x, curve.data[3].point.y); } } /** * cpml_arc_to_curves: * @arc: (in): the #CpmlPrimitive arc data * @segment: (out): the destination #CpmlSegment * @n_curves: (in): number of Bézier to use * * Converts @arc to a serie of @n_curves Bézier curves and puts them * inside @segment. Obviously, @segment must have enough space to * contain at least @n_curves curves. * * This function works in a similar way as cpml_arc_to_cairo() but * has two important differences: it does not need a cairo context - * and the number of curves to be generated is explicitely defined. + * and the number of curves to be generated is explicitly defined. * The latter difference allows a more specific error control from * the application: in the file src/cairo-arc.c, found in the cairo * tarball (at least in cairo-1.9.1), there is a table showing the * magnitude of error of this curve approximation algorithm. * * Since: 1.0 **/ void cpml_arc_to_curves(const CpmlPrimitive *arc, CpmlSegment *segment, size_t n_curves) { CpmlPair center; double r, start, end; double step, angle; CpmlPrimitive curve; if (!cpml_arc_info(arc, ¢er, &r, &start, &end)) return; step = (end-start) / (double) n_curves; segment->num_data = n_curves*4; curve.segment = segment; curve.data = segment->data; for (angle = start; n_curves--; angle += step) { arc_to_curve(&curve, ¢er, r, angle, angle+step); curve.data += 4; } } static double get_length(const CpmlPrimitive *arc) { double r, start, end, delta; if (!cpml_arc_info(arc, NULL, &r, &start, &end) || start == end) return 0.; delta = end - start; if (delta < 0) delta += M_PI*2; return r*delta; } static void put_extents(const CpmlPrimitive *arc, CpmlExtents *extents) { double r, start, end; CpmlPair center, pair; extents->is_defined = 0; if (!cpml_arc_info(arc, ¢er, &r, &start, &end)) return; /* Add the right quadrant point if needed */ if (ANGLE_INCLUDED(0) || ANGLE_INCLUDED(M_PI * 2)) { pair.x = center.x + r; pair.y = center.y; cpml_extents_pair_add(extents, &pair); } /* Add the bottom quadrant point if needed */ if (ANGLE_INCLUDED(M_PI_2) || ANGLE_INCLUDED(M_PI_2 * 5)) { pair.x = center.x; pair.y = center.y + r; cpml_extents_pair_add(extents, &pair); } /* Add the left quadrant point if needed */ if (ANGLE_INCLUDED(M_PI)) { pair.x = center.x - r; pair.y = center.y; cpml_extents_pair_add(extents, &pair); } /* Add the top quadrant point if needed */ if (ANGLE_INCLUDED(M_PI_2 * 3) || ANGLE_INCLUDED(-M_PI_2)) { pair.x = center.x; pair.y = center.y - r; cpml_extents_pair_add(extents, &pair); } /* Add the start point */ cpml_primitive_put_point(arc, 0, &pair); cpml_extents_pair_add(extents, &pair); /* Add the end point */ cpml_primitive_put_point(arc, -1, &pair); cpml_extents_pair_add(extents, &pair); } static void put_pair_at(const CpmlPrimitive *arc, double pos, CpmlPair *pair) { if (pos == 0.) { cpml_pair_from_cairo(pair, arc->org); } else if (pos == 1.) { cpml_pair_from_cairo(pair, &arc->data[2]); } else { CpmlPair center; double r, start, end, angle; if (!cpml_arc_info(arc, ¢er, &r, &start, &end)) return; angle = (end-start)*pos + start; cpml_vector_from_angle(pair, angle); cpml_vector_set_length(pair, r); pair->x += center.x; pair->y += center.y; } } static void put_vector_at(const CpmlPrimitive *arc, double pos, CpmlVector *vector) { double start, end, angle; if (!cpml_arc_info(arc, NULL, NULL, &start, &end)) return; angle = (end-start)*pos + start; cpml_vector_from_angle(vector, angle); cpml_vector_normal(vector); if (start > end) { vector->x = -vector->x; vector->y = -vector->y; } } static size_t put_intersections(const CpmlPrimitive *arc, const CpmlPrimitive *primitive, size_t n_dest, CpmlPair *dest) { CpmlPair center; double r; cpml_arc_info(arc, ¢er, &r, NULL, NULL); switch ((int) cpml_primitive_type(primitive)) { case CPML_LINE: { CpmlPair p1, p2; cpml_primitive_put_point(primitive, 0, &p1); cpml_primitive_put_point(primitive, -1, &p2); return circle_line(¢er, r, &p1, &p2, dest); } case CPML_ARC: /* TODO: Not Yet Implemented */ return 0; } return 0; } static void offset(CpmlPrimitive *arc, double offset) { CpmlPair p[3], center; double r; cpml_pair_from_cairo(&p[0], arc->org); cpml_pair_from_cairo(&p[1], &arc->data[1]); cpml_pair_from_cairo(&p[2], &arc->data[2]); if (!get_center(p, ¢er)) return; r = cpml_pair_distance(&p[0], ¢er) + offset; /* Offset the three points by calculating their vector from the center, * setting the new radius as length and readding the center */ p[0].x -= center.x; p[0].y -= center.y; p[1].x -= center.x; p[1].y -= center.y; p[2].x -= center.x; p[2].y -= center.y; cpml_vector_set_length(&p[0], r); cpml_vector_set_length(&p[1], r); cpml_vector_set_length(&p[2], r); p[0].x += center.x; p[0].y += center.y; p[1].x += center.x; p[1].y += center.y; p[2].x += center.x; p[2].y += center.y; cpml_pair_to_cairo(&p[0], arc->org); cpml_pair_to_cairo(&p[1], &arc->data[1]); cpml_pair_to_cairo(&p[2], &arc->data[2]); } static int get_center(const CpmlPair *p, CpmlPair *dest) { CpmlPair b, c; double d, b2, c2; /* When p[0] == p[2], p[0]..p[1] is considered the diameter of a circle */ if (p[0].x == p[2].x && p[0].y == p[2].y) { dest->x = (p[0].x + p[1].x) / 2; dest->y = (p[0].y + p[1].y) / 2; return 1; } /* Translate the 3 points of -p0, to simplify the formula */ b.x = p[1].x - p[0].x; b.y = p[1].y - p[0].y; c.x = p[2].x - p[0].x; c.y = p[2].y - p[0].y; /* Check for division by 0, that is the case where the 3 given points * are laying on a straight line and there is no fitting circle */ d = (b.x*c.y - b.y*c.x) * 2; if (d == 0.) return 0; b2 = b.x*b.x + b.y*b.y; c2 = c.x*c.x + c.y*c.y; dest->x = (c.y*b2 - b.y*c2) / d + p[0].x; dest->y = (b.x*c2 - c.x*b2) / d + p[0].y; return 1; } static void get_angles(const CpmlPair *p, const CpmlPair *center, double *start, double *end) { CpmlVector vector; double mid; /* Calculate the starting angle */ vector.x = p[0].x - center->x; vector.y = p[0].y - center->y; *start = cpml_vector_angle(&vector); if (p[0].x == p[2].x && p[0].y == p[2].y) { /* When p[0] and p[2] are cohincidents, p[0]..p[1] is the diameter * of a circle: return by convention start=start end=start+2PI */ *end = *start + M_PI*2; } else { /* Calculate the mid and end angle: cpml_vector_angle() * returns an angle between -M_PI and M_PI */ vector.x = p[1].x - center->x; vector.y = p[1].y - center->y; mid = cpml_vector_angle(&vector); vector.x = p[2].x - center->x; vector.y = p[2].y - center->y; *end = cpml_vector_angle(&vector); if (*end > *start) { /* If the middle angle is outside the start..end range, * the arc should be reversed (that is, start must * be greather than end) */ if (mid < *start || mid > *end) *start += M_PI*2; } else { /* Here the arc is reversed: if the middle angle is * outside the end..start range, the arc should be * re-reversed to get a straight arc (that is, end * must be greather than start) */ if (mid < *end || mid > *start) *end += M_PI*2; } } } static void arc_to_curve(CpmlPrimitive *curve, const CpmlPair *center, double r, double start, double end) { double r_sin1, r_cos1; double r_sin2, r_cos2; double h; r_sin1 = r*sin(start); r_cos1 = r*cos(start); r_sin2 = r*sin(end); r_cos2 = r*cos(end); h = 4./3. * tan((end-start) / 4.); curve->data[0].header.type = CPML_CURVE; curve->data[0].header.length = 4; curve->data[1].point.x = center->x + r_cos1 - h*r_sin1; curve->data[1].point.y = center->y + r_sin1 + h*r_cos1; curve->data[2].point.x = center->x + r_cos2 + h*r_sin2; curve->data[2].point.y = center->y + r_sin2 - h*r_cos2; curve->data[3].point.x = center->x + r_cos2; curve->data[3].point.y = center->y + r_sin2; } static int circle_line(const CpmlPair *center, double r, const CpmlPair *p1, const CpmlPair *p2, CpmlPair *dest) { double x1, y1, x2, y2; double a, b, c, d, e; /* Translate to have the circle origin in (0, 0) */ x1 = p1->x - center->x; y1 = p1->y - center->y; x2 = p2->x - center->x; y2 = p2->y - center->y; /* Equation of line: ax + by = c * Equation of circle: x² + y² = r² */ a = y2 - y1; b = x1 - x2; c = x1*y2 - x2*y1; d = r*r * (a*a + b*b) - c*c; if (d < 0) { /* No intersections found */ return 0; } d = sqrt(d); e = a*a + b*b; dest->x = (a*c - b*d) / e + center->x; dest->y = (b*c + a*d) / e + center->y; if (d == 0) { /* Only one soultion found (tangent line) */ return 1; } ++ dest; dest->x = (a*c + b*d) / e + center->x; dest->y = (b*c - a*d) / e + center->y; return 2; } diff --git a/src/cpml/cpml-primitive-private.h b/src/cpml/cpml-primitive-private.h index 247c94c6..cacb8763 100644 --- a/src/cpml/cpml-primitive-private.h +++ b/src/cpml/cpml-primitive-private.h @@ -1,92 +1,92 @@ /* CPML - Cairo Path Manipulation Library * Copyright (C) 2007-2022 Nicola Fontana * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __CPML_PRIMITIVE_PRIVATE_H__ #define __CPML_PRIMITIVE_PRIVATE_H__ CAIRO_BEGIN_DECLS /** * _CpmlPrimitiveClass: * @name: descriptive name of the primitive type. This name * will be used for debugging purpose and while * dumping the primitive data. * @n_points: exact number of points needed to properly define * a primitive of this class type. * @get_length: gets the length of a primitive. * @put_extents: gets the bounding box of a primitive. * @put_pair_at: gets the coordinates of a point on the primitive at * a given factor. * @put_vector_at: gets the vector of a point on the primitive at a * given factor. * @get_closest_pos: gets the factor of the point on a primitive closest * to another given point. * @put_intersections: gets the intersection points between a primitive of * this type and a primitive of any type. The number - * of returned intersections can be explicitely limited. + * of returned intersections can be explicitly limited. * @offset: creates a new primitive of tha same type parallel * to the original one with a given distance. * @join: join two primitives (the first one of this class type) * by modifying the end point of the first one and the * start point of the second one. * * Any primitive type must implement an instance of this class as a * global variable. This will abstract the primitives and allows to * access them through the cpml_primitive_...() APIs. */ typedef struct __CpmlPrimitiveClass _CpmlPrimitiveClass; struct __CpmlPrimitiveClass { const char *name; size_t n_points; double (*get_length) (const CpmlPrimitive *primitive); void (*put_extents) (const CpmlPrimitive *primitive, CpmlExtents *extents); void (*put_pair_at) (const CpmlPrimitive *primitive, double pos, CpmlPair *pair); void (*put_vector_at) (const CpmlPrimitive *primitive, double pos, CpmlVector *vector); double (*get_closest_pos) (const CpmlPrimitive *primitive, const CpmlPair *pair); size_t (*put_intersections) (const CpmlPrimitive *primitive, const CpmlPrimitive *primitive2, size_t n_dest, CpmlPair *dest); void (*offset) (CpmlPrimitive *primitive, double offset); int (*join) (CpmlPrimitive *primitive, CpmlPrimitive *primitive2); }; const _CpmlPrimitiveClass * _cpml_line_get_class (void); const _CpmlPrimitiveClass * _cpml_arc_get_class (void); const _CpmlPrimitiveClass * _cpml_curve_get_class (void); const _CpmlPrimitiveClass * _cpml_close_get_class (void); CAIRO_END_DECLS #endif /* __CPML_PRIMITIVE_PRIVATE_H__ */