diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2010-06-14 06:37:27 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2010-06-14 09:31:58 +0800 |
commit | e6972011f01eab9f8d0a4584f32ee1e2a00f3231 (patch) | |
tree | 69e2a5e846965c3b369724b825f5b35c6d88056a | |
parent | 7f3377c78a560aa762e04d596b79f847c4acd870 (diff) | |
download | gsoc2013-evolution-e6972011f01eab9f8d0a4584f32ee1e2a00f3231.tar.gz gsoc2013-evolution-e6972011f01eab9f8d0a4584f32ee1e2a00f3231.tar.zst gsoc2013-evolution-e6972011f01eab9f8d0a4584f32ee1e2a00f3231.zip |
Embed libart_lgpl and libgnomecanvas.
Both of these modules are deprecated and going away in GNOME 3 but we
still rely heavily on them for GnomeCalendar and ETable. So, welcome
to the island of unwanted libraries...
143 files changed, 36835 insertions, 9 deletions
diff --git a/Makefile.am b/Makefile.am index 7d55435953..a69c68694e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,8 @@ SUBDIRS = \ m4 \ data \ smclient \ + libart_lgpl \ + libgnomecanvas \ e-util \ a11y \ filter \ diff --git a/configure.ac b/configure.ac index c493cb926f..4a18cc3959 100644 --- a/configure.ac +++ b/configure.ac @@ -41,9 +41,10 @@ m4_define([eds_minimum_version], [evo_version]) m4_define([gnome_icon_theme_minimum_version], [2.19.91]) m4_define([gnome_desktop_minimum_version], [2.26.0]) m4_define([libgtkhtml_minimum_version], [3.31.2]) +m4_define([gail_minimum_version], [1.9.0]) dnl For libgnomecanvas +m4_define([pangoft2_minimum_version], [1.0.1]) dnl For libgnomecanvas m4_define([gconf_minimum_version], [2.0.0]) dnl XXX Just a Guess m4_define([libsoup_minimum_version], [2.4.0]) dnl XXX Just a Guess -m4_define([libgnomecanvas_minimum_version], [2.0.0]) dnl XXX Just a Guess m4_define([libxml_minimum_version], [2.7.3]) m4_define([shared_mime_info_minimum_version], [0.22]) m4_define([libpst_minimum_version], [0.6.41]) @@ -221,9 +222,10 @@ PKG_CHECK_MODULES([GNOME_PLATFORM], [glib-2.0 >= glib_minimum_version gthread-2.0 >= glib_minimum_version gtk+-2.0 >= gtk_minimum_version + gail >= gail_minimum_version gconf-2.0 >= gconf_minimum_version - libgnomecanvas-2.0 >= libgnomecanvas_minimum_version libxml-2.0 >= libxml_minimum_version + pangoft2 >= pangoft2_minimum_version shared-mime-info >= shared_mime_info_minimum_version gnome-desktop-2.0 >= gnome_desktop_minimum_version unique-1.0 >= unique_minimum_version]) @@ -1721,6 +1723,8 @@ help/quickref/pl/Makefile help/quickref/pt/Makefile help/quickref/sv/Makefile help/quickref/sq/Makefile +libart_lgpl/Makefile +libgnomecanvas/Makefile shell/Makefile shell/evolution-nognome shell/test/Makefile diff --git a/libart_lgpl/AUTHORS b/libart_lgpl/AUTHORS new file mode 100644 index 0000000000..fbb51b36a4 --- /dev/null +++ b/libart_lgpl/AUTHORS @@ -0,0 +1 @@ +Raph Levien <raph@acm.org> diff --git a/libart_lgpl/COPYING b/libart_lgpl/COPYING new file mode 100644 index 0000000000..bf50f20de6 --- /dev/null +++ b/libart_lgpl/COPYING @@ -0,0 +1,482 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307 USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/libart_lgpl/MAINTAINERS b/libart_lgpl/MAINTAINERS new file mode 100644 index 0000000000..dfeac41c66 --- /dev/null +++ b/libart_lgpl/MAINTAINERS @@ -0,0 +1,7 @@ +Alex Larsson +E-mail: alexl@redhat.com +Userid: alexl + +Kjartan Maraas +E-mail: kmaraas@gnome.org +Userid: kmaraas diff --git a/libart_lgpl/Makefile.am b/libart_lgpl/Makefile.am new file mode 100644 index 0000000000..8a3eb2e755 --- /dev/null +++ b/libart_lgpl/Makefile.am @@ -0,0 +1,103 @@ +privsolib_LTLIBRARIES = libart_lgpl_2.la + +if OS_WIN32 +else +libm = -lm +endif + +libart_lgpl_2_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + $(GNOME_PLATFORM_CFLAGS) + +libart_lgpl_2_la_SOURCES = \ + art_affine.c \ + art_affine.h \ + art_alphagamma.c \ + art_alphagamma.h \ + art_bpath.c \ + art_bpath.h \ + art_filterlevel.h \ + art_gray_svp.c \ + art_gray_svp.h \ + art_misc.c \ + art_misc.h \ + art_pathcode.h \ + art_pixbuf.c \ + art_pixbuf.h \ + art_point.h \ + art_rect.c \ + art_rect.h \ + art_rect_svp.c \ + art_rect_svp.h \ + art_rect_uta.c \ + art_rect_uta.h \ + art_render.c \ + art_render.h \ + art_render_gradient.c \ + art_render_gradient.h \ + art_render_mask.c \ + art_render_mask.h \ + art_render_svp.c \ + art_render_svp.h \ + art_rgb.c \ + art_rgb.h \ + art_rgb_a_affine.c \ + art_rgb_a_affine.h \ + art_rgb_affine.c \ + art_rgb_affine.h \ + art_rgb_affine_private.c \ + art_rgb_affine_private.h \ + art_rgb_bitmap_affine.c \ + art_rgb_bitmap_affine.h \ + art_rgb_pixbuf_affine.c \ + art_rgb_pixbuf_affine.h \ + art_rgb_rgba_affine.c \ + art_rgb_rgba_affine.h \ + art_rgb_svp.c \ + art_rgb_svp.h \ + art_rgba.c \ + art_rgba.h \ + art_svp.c \ + art_svp.h \ + art_svp_intersect.c \ + art_svp_intersect.h \ + art_svp_ops.c \ + art_svp_ops.h \ + art_svp_point.c \ + art_svp_point.h \ + art_svp_render_aa.c \ + art_svp_render_aa.h \ + art_svp_vpath.c \ + art_svp_vpath.h \ + art_svp_vpath_stroke.c \ + art_svp_vpath_stroke.h \ + art_svp_wind.c \ + art_svp_wind.h \ + art_uta.c \ + art_uta.h \ + art_uta_ops.c \ + art_uta_ops.h \ + art_uta_rect.c \ + art_uta_rect.h \ + art_uta_svp.c \ + art_uta_svp.h \ + art_uta_vpath.c \ + art_uta_vpath.h \ + art_vpath.c \ + art_vpath.h \ + art_vpath_bpath.c \ + art_vpath_bpath.h \ + art_vpath_dash.c \ + art_vpath_dash.h \ + art_vpath_svp.c \ + art_vpath_svp.h \ + libart.h + +libart_lgpl_2_la_LDFLAGS = $(NO_UNDEFINED) + +libart_lgpl_2_la_LIBADD = \ + $(GNOME_PLATFORM_LIBS) \ + $(libm) + +-include $(top_srcdir)/git.mk diff --git a/libart_lgpl/README b/libart_lgpl/README new file mode 100644 index 0000000000..882527e53f --- /dev/null +++ b/libart_lgpl/README @@ -0,0 +1,19 @@ +This is the LGPL'd component of libart. All functions needed for +running the Gnome canvas, and for printing support, will be going in +here. The GPL'd component will be getting various enhanced functions +for specific applications. + +Libart is free software. It is also for sale. For information about +licensing libart, please contact Raph Levien +<raph@acm.org>. Contributions to the codebase are also very welcome, +but the copyright must be assigned in writing to preserve the +licensing flexibility. + + +For more information about libart, see the web page: + + http://www.levien.com/libart/ + +There's also a libart tutorial available at +http://www.gnome.org/~mathieu/libart/libart.html + diff --git a/libart_lgpl/README.CVS b/libart_lgpl/README.CVS new file mode 100644 index 0000000000..7ba42f7e70 --- /dev/null +++ b/libart_lgpl/README.CVS @@ -0,0 +1,14 @@ +Welcome to the libart source tree! + +This code is being developed in a "free software for sale" +model. Thus, it's available under a free software license (LGPL for +this module), but I'm also requesting that for all changes to the code +the copyright gets assigned back to me. + +So if you want to contribute, please do, but contact me about getting +the copyright assigned. Otherwise, I will have to back your changes +out. + +Thanks! + +Raph Levien <raph@acm.org> diff --git a/libart_lgpl/art_affine.c b/libart_lgpl/art_affine.c new file mode 100644 index 0000000000..9f332a3520 --- /dev/null +++ b/libart_lgpl/art_affine.c @@ -0,0 +1,458 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Simple manipulations with affine transformations */ + +#include "config.h" +#include "art_affine.h" +#include "art_misc.h" /* for M_PI */ + +#include <math.h> +#include <stdio.h> /* for sprintf */ +#include <string.h> /* for strcpy */ + + +/* According to a strict interpretation of the libart structure, this + routine should go into its own module, art_point_affine. However, + it's only two lines of code, and it can be argued that it is one of + the natural basic functions of an affine transformation. +*/ + +/** + * art_affine_point: Do an affine transformation of a point. + * @dst: Where the result point is stored. + * @src: The original point. + @ @affine: The affine transformation. + **/ +void +art_affine_point (ArtPoint *dst, const ArtPoint *src, + const double affine[6]) +{ + double x, y; + + x = src->x; + y = src->y; + dst->x = x * affine[0] + y * affine[2] + affine[4]; + dst->y = x * affine[1] + y * affine[3] + affine[5]; +} + +/** + * art_affine_invert: Find the inverse of an affine transformation. + * @dst: Where the resulting affine is stored. + * @src: The original affine transformation. + * + * All non-degenerate affine transforms are invertible. If the original + * affine is degenerate or nearly so, expect numerical instability and + * very likely core dumps on Alpha and other fp-picky architectures. + * Otherwise, @dst multiplied with @src, or @src multiplied with @dst + * will be (to within roundoff error) the identity affine. + **/ +void +art_affine_invert (double dst[6], const double src[6]) +{ + double r_det; + + r_det = 1.0 / (src[0] * src[3] - src[1] * src[2]); + dst[0] = src[3] * r_det; + dst[1] = -src[1] * r_det; + dst[2] = -src[2] * r_det; + dst[3] = src[0] * r_det; + dst[4] = -src[4] * dst[0] - src[5] * dst[2]; + dst[5] = -src[4] * dst[1] - src[5] * dst[3]; +} + +/** + * art_affine_flip: Flip an affine transformation horizontally and/or vertically. + * @dst_affine: Where the resulting affine is stored. + * @src_affine: The original affine transformation. + * @horiz: Whether or not to flip horizontally. + * @vert: Whether or not to flip horizontally. + * + * Flips the affine transform. FALSE for both @horiz and @vert implements + * a simple copy operation. TRUE for both @horiz and @vert is a + * 180 degree rotation. It is ok for @src_affine and @dst_affine to + * be equal pointers. + **/ +void +art_affine_flip (double dst_affine[6], const double src_affine[6], int horz, int vert) +{ + dst_affine[0] = horz ? - src_affine[0] : src_affine[0]; + dst_affine[1] = horz ? - src_affine[1] : src_affine[1]; + dst_affine[2] = vert ? - src_affine[2] : src_affine[2]; + dst_affine[3] = vert ? - src_affine[3] : src_affine[3]; + dst_affine[4] = horz ? - src_affine[4] : src_affine[4]; + dst_affine[5] = vert ? - src_affine[5] : src_affine[5]; +} + +#define EPSILON 1e-6 + +/* It's ridiculous I have to write this myself. This is hardcoded to + six digits of precision, which is good enough for PostScript. + + The return value is the number of characters (i.e. strlen (str)). + It is no more than 12. */ +static int +art_ftoa (char str[80], double x) +{ + char *p = str; + int i, j; + + p = str; + if (fabs (x) < EPSILON / 2) + { + strcpy (str, "0"); + return 1; + } + if (x < 0) + { + *p++ = '-'; + x = -x; + } + if ((int)floor ((x + EPSILON / 2) < 1)) + { + *p++ = '0'; + *p++ = '.'; + i = sprintf (p, "%06d", (int)floor ((x + EPSILON / 2) * 1e6)); + while (i && p[i - 1] == '0') + i--; + if (i == 0) + i--; + p += i; + } + else if (x < 1e6) + { + i = sprintf (p, "%d", (int)floor (x + EPSILON / 2)); + p += i; + if (i < 6) + { + int ix; + + *p++ = '.'; + x -= floor (x + EPSILON / 2); + for (j = i; j < 6; j++) + x *= 10; + ix = floor (x + 0.5); + + for (j = 0; j < i; j++) + ix *= 10; + + /* A cheap hack, this routine can round wrong for fractions + near one. */ + if (ix == 1000000) + ix = 999999; + + sprintf (p, "%06d", ix); + i = 6 - i; + while (i && p[i - 1] == '0') + i--; + if (i == 0) + i--; + p += i; + } + } + else + p += sprintf (p, "%g", x); + + *p = '\0'; + return p - str; +} + + + +#include <stdlib.h> +/** + * art_affine_to_string: Convert affine transformation to concise PostScript string representation. + * @str: Where to store the resulting string. + * @src: The affine transform. + * + * Converts an affine transform into a bit of PostScript code that + * implements the transform. Special cases of scaling, rotation, and + * translation are detected, and the corresponding PostScript + * operators used (this greatly aids understanding the output + * generated). The identity transform is mapped to the null string. + **/ +void +art_affine_to_string (char str[128], const double src[6]) +{ + char tmp[80]; + int i, ix; + +#if 0 + for (i = 0; i < 1000; i++) + { + double d = rand () * .1 / RAND_MAX; + art_ftoa (tmp, d); + printf ("%g %f %s\n", d, d, tmp); + } +#endif + if (fabs (src[4]) < EPSILON && fabs (src[5]) < EPSILON) + { + /* could be scale or rotate */ + if (fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON) + { + /* scale */ + if (fabs (src[0] - 1) < EPSILON && fabs (src[3] - 1) < EPSILON) + { + /* identity transform */ + str[0] = '\0'; + return; + } + else + { + ix = 0; + ix += art_ftoa (str + ix, src[0]); + str[ix++] = ' '; + ix += art_ftoa (str + ix, src[3]); + strcpy (str + ix, " scale"); + return; + } + } + else + { + /* could be rotate */ + if (fabs (src[0] - src[3]) < EPSILON && + fabs (src[1] + src[2]) < EPSILON && + fabs (src[0] * src[0] + src[1] * src[1] - 1) < 2 * EPSILON) + { + double theta; + + theta = (180 / M_PI) * atan2 (src[1], src[0]); + art_ftoa (tmp, theta); + sprintf (str, "%s rotate", tmp); + return; + } + } + } + else + { + /* could be translate */ + if (fabs (src[0] - 1) < EPSILON && fabs (src[1]) < EPSILON && + fabs (src[2]) < EPSILON && fabs (src[3] - 1) < EPSILON) + { + ix = 0; + ix += art_ftoa (str + ix, src[4]); + str[ix++] = ' '; + ix += art_ftoa (str + ix, src[5]); + strcpy (str + ix, " translate"); + return; + } + } + + ix = 0; + str[ix++] = '['; + str[ix++] = ' '; + for (i = 0; i < 6; i++) + { + ix += art_ftoa (str + ix, src[i]); + str[ix++] = ' '; + } + strcpy (str + ix, "] concat"); +} + +/** + * art_affine_multiply: Multiply two affine transformation matrices. + * @dst: Where to store the result. + * @src1: The first affine transform to multiply. + * @src2: The second affine transform to multiply. + * + * Multiplies two affine transforms together, i.e. the resulting @dst + * is equivalent to doing first @src1 then @src2. Note that the + * PostScript concat operator multiplies on the left, i.e. "M concat" + * is equivalent to "CTM = multiply (M, CTM)"; + * + * It is safe to call this function with @dst equal to @src1 or @src2. + **/ +void +art_affine_multiply (double dst[6], const double src1[6], const double src2[6]) +{ + double d0, d1, d2, d3, d4, d5; + + d0 = src1[0] * src2[0] + src1[1] * src2[2]; + d1 = src1[0] * src2[1] + src1[1] * src2[3]; + d2 = src1[2] * src2[0] + src1[3] * src2[2]; + d3 = src1[2] * src2[1] + src1[3] * src2[3]; + d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4]; + d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5]; + dst[0] = d0; + dst[1] = d1; + dst[2] = d2; + dst[3] = d3; + dst[4] = d4; + dst[5] = d5; +} + +/** + * art_affine_identity: Set up the identity matrix. + * @dst: Where to store the resulting affine transform. + * + * Sets up an identity matrix. + **/ +void +art_affine_identity (double dst[6]) +{ + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + dst[3] = 1; + dst[4] = 0; + dst[5] = 0; +} + + +/** + * art_affine_scale: Set up a scaling matrix. + * @dst: Where to store the resulting affine transform. + * @sx: X scale factor. + * @sy: Y scale factor. + * + * Sets up a scaling matrix. + **/ +void +art_affine_scale (double dst[6], double sx, double sy) +{ + dst[0] = sx; + dst[1] = 0; + dst[2] = 0; + dst[3] = sy; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_rotate: Set up a rotation affine transform. + * @dst: Where to store the resulting affine transform. + * @theta: Rotation angle in degrees. + * + * Sets up a rotation matrix. In the standard libart coordinate + * system, in which increasing y moves downward, this is a + * counterclockwise rotation. In the standard PostScript coordinate + * system, which is reversed in the y direction, it is a clockwise + * rotation. + **/ +void +art_affine_rotate (double dst[6], double theta) +{ + double s, c; + + s = sin (theta * M_PI / 180.0); + c = cos (theta * M_PI / 180.0); + dst[0] = c; + dst[1] = s; + dst[2] = -s; + dst[3] = c; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_shear: Set up a shearing matrix. + * @dst: Where to store the resulting affine transform. + * @theta: Shear angle in degrees. + * + * Sets up a shearing matrix. In the standard libart coordinate system + * and a small value for theta, || becomes \\. Horizontal lines remain + * unchanged. + **/ +void +art_affine_shear (double dst[6], double theta) +{ + double t; + + t = tan (theta * M_PI / 180.0); + dst[0] = 1; + dst[1] = 0; + dst[2] = t; + dst[3] = 1; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_translate: Set up a translation matrix. + * @dst: Where to store the resulting affine transform. + * @tx: X translation amount. + * @tx: Y translation amount. + * + * Sets up a translation matrix. + **/ +void +art_affine_translate (double dst[6], double tx, double ty) +{ + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + dst[3] = 1; + dst[4] = tx; + dst[5] = ty; +} + +/** + * art_affine_expansion: Find the affine's expansion factor. + * @src: The affine transformation. + * + * Finds the expansion factor, i.e. the square root of the factor + * by which the affine transform affects area. In an affine transform + * composed of scaling, rotation, shearing, and translation, returns + * the amount of scaling. + * + * Return value: the expansion factor. + **/ +double +art_affine_expansion (const double src[6]) +{ + return sqrt (fabs (src[0] * src[3] - src[1] * src[2])); +} + +/** + * art_affine_rectilinear: Determine whether the affine transformation is rectilinear. + * @src: The original affine transformation. + * + * Determines whether @src is rectilinear, i.e. grid-aligned + * rectangles are transformed to other grid-aligned rectangles. The + * implementation has epsilon-tolerance for roundoff errors. + * + * Return value: TRUE if @src is rectilinear. + **/ +int +art_affine_rectilinear (const double src[6]) +{ + return ((fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON) || + (fabs (src[0]) < EPSILON && fabs (src[3]) < EPSILON)); +} + +/** + * art_affine_equal: Determine whether two affine transformations are equal. + * @matrix1: An affine transformation. + * @matrix2: Another affine transformation. + * + * Determines whether @matrix1 and @matrix2 are equal, with + * epsilon-tolerance for roundoff errors. + * + * Return value: TRUE if @matrix1 and @matrix2 are equal. + **/ +int +art_affine_equal (double matrix1[6], double matrix2[6]) +{ + return (fabs (matrix1[0] - matrix2[0]) < EPSILON && + fabs (matrix1[1] - matrix2[1]) < EPSILON && + fabs (matrix1[2] - matrix2[2]) < EPSILON && + fabs (matrix1[3] - matrix2[3]) < EPSILON && + fabs (matrix1[4] - matrix2[4]) < EPSILON && + fabs (matrix1[5] - matrix2[5]) < EPSILON); +} diff --git a/libart_lgpl/art_affine.h b/libart_lgpl/art_affine.h new file mode 100644 index 0000000000..0baee70941 --- /dev/null +++ b/libart_lgpl/art_affine.h @@ -0,0 +1,89 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_AFFINE_H__ +#define __ART_AFFINE_H__ + +#include <libart_lgpl/art_point.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_affine_point (ArtPoint *dst, const ArtPoint *src, + const double affine[6]); + +void +art_affine_invert (double dst_affine[6], const double src_affine[6]); + +/* flip the matrix, FALSE, FALSE is a simple copy operation, and + TRUE, TRUE equals a rotation by 180 degrees */ +void +art_affine_flip (double dst_affine[6], const double src_affine[6], + int horz, int vert); + +void +art_affine_to_string (char str[128], const double src[6]); + +void +art_affine_multiply (double dst[6], + const double src1[6], const double src2[6]); + +/* set up the identity matrix */ +void +art_affine_identity (double dst[6]); + +/* set up a scaling matrix */ +void +art_affine_scale (double dst[6], double sx, double sy); + +/* set up a rotation matrix; theta is given in degrees */ +void +art_affine_rotate (double dst[6], double theta); + +/* set up a shearing matrix; theta is given in degrees */ +void +art_affine_shear (double dst[6], double theta); + +/* set up a translation matrix */ +void +art_affine_translate (double dst[6], double tx, double ty); + + +/* find the affine's "expansion factor", i.e. the scale amount */ +double +art_affine_expansion (const double src[6]); + +/* Determine whether the affine transformation is rectilinear, + i.e. whether a rectangle aligned to the grid is transformed into + another rectangle aligned to the grid. */ +int +art_affine_rectilinear (const double src[6]); + +/* Determine whether two affine transformations are equal within grid allignment */ +int +art_affine_equal (double matrix1[6], double matrix2[6]); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_AFFINE_H__ */ diff --git a/libart_lgpl/art_alphagamma.c b/libart_lgpl/art_alphagamma.c new file mode 100644 index 0000000000..4651883cf5 --- /dev/null +++ b/libart_lgpl/art_alphagamma.c @@ -0,0 +1,85 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Some functions to build alphagamma tables */ + +#include "config.h" +#include "art_alphagamma.h" + +#include <math.h> + +/** + * art_alphagamma_new: Create a new #ArtAlphaGamma. + * @gamma: Gamma value. + * + * Create a new #ArtAlphaGamma for a specific value of @gamma. When + * correctly implemented (which is generally not the case in libart), + * alpha compositing with an alphagamma parameter is equivalent to + * applying the gamma transformation to source images, doing the alpha + * compositing (in linear intensity space), then applying the inverse + * gamma transformation, bringing it back to a gamma-adjusted + * intensity space. + * + * Return value: The newly created #ArtAlphaGamma. + **/ +ArtAlphaGamma * +art_alphagamma_new (double gamma) +{ + int tablesize; + ArtAlphaGamma *alphagamma; + int i; + int *table; + art_u8 *invtable; + double s, r_gamma; + + tablesize = ceil (gamma * 8); + if (tablesize < 10) + tablesize = 10; + + alphagamma = (ArtAlphaGamma *)art_alloc (sizeof(ArtAlphaGamma) + + ((1 << tablesize) - 1) * + sizeof(art_u8)); + alphagamma->gamma = gamma; + alphagamma->invtable_size = tablesize; + + table = alphagamma->table; + for (i = 0; i < 256; i++) + table[i] = (int)floor (((1 << tablesize) - 1) * + pow (i * (1.0 / 255), gamma) + 0.5); + + invtable = alphagamma->invtable; + s = 1.0 / ((1 << tablesize) - 1); + r_gamma = 1.0 / gamma; + for (i = 0; i < 1 << tablesize; i++) + invtable[i] = (int)floor (255 * pow (i * s, r_gamma) + 0.5); + + return alphagamma; +} + +/** + * art_alphagamma_free: Free an #ArtAlphaGamma. + * @alphagamma: An #ArtAlphaGamma. + * + * Frees the #ArtAlphaGamma. + **/ +void +art_alphagamma_free (ArtAlphaGamma *alphagamma) +{ + art_free (alphagamma); +} diff --git a/libart_lgpl/art_alphagamma.h b/libart_lgpl/art_alphagamma.h new file mode 100644 index 0000000000..5f766c95ba --- /dev/null +++ b/libart_lgpl/art_alphagamma.h @@ -0,0 +1,51 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_ALPHAGAMMA_H__ +#define __ART_ALPHAGAMMA_H__ + +/* Alphagamma tables */ + +#include <libart_lgpl/art_misc.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtAlphaGamma ArtAlphaGamma; + +struct _ArtAlphaGamma { + /*< private >*/ + double gamma; + int invtable_size; + int table[256]; + art_u8 invtable[1]; +}; + +ArtAlphaGamma * +art_alphagamma_new (double gamma); + +void +art_alphagamma_free (ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_H__ */ diff --git a/libart_lgpl/art_bpath.c b/libart_lgpl/art_bpath.c new file mode 100644 index 0000000000..a25acbf95d --- /dev/null +++ b/libart_lgpl/art_bpath.c @@ -0,0 +1,92 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for bezier paths */ + +#include "config.h" +#include "art_bpath.h" + +#include <math.h> + + +/** + * art_bpath_affine_transform: Affine transform an #ArtBpath. + * @src: The source #ArtBpath. + * @matrix: The affine transform. + * + * Affine transform the bezpath, returning a newly allocated #ArtBpath + * (allocated using art_alloc()). + * + * Result (x', y') = (matrix[0] * x + matrix[2] * y + matrix[4], + * matrix[1] * x + matrix[3] * y + matrix[5]) + * + * Return value: the transformed #ArtBpath. + **/ +ArtBpath * +art_bpath_affine_transform (const ArtBpath *src, const double matrix[6]) +{ + int i; + int size; + ArtBpath *new; + ArtPathcode code; + double x, y; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtBpath, size + 1); + + for (i = 0; i < size; i++) + { + code = src[i].code; + new[i].code = code; + if (code == ART_CURVETO) + { + x = src[i].x1; + y = src[i].y1; + new[i].x1 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y1 = matrix[1] * x + matrix[3] * y + matrix[5]; + x = src[i].x2; + y = src[i].y2; + new[i].x2 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y2 = matrix[1] * x + matrix[3] * y + matrix[5]; + } + else + { + new[i].x1 = 0; + new[i].y1 = 0; + new[i].x2 = 0; + new[i].y2 = 0; + } + x = src[i].x3; + y = src[i].y3; + new[i].x3 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y3 = matrix[1] * x + matrix[3] * y + matrix[5]; + } + new[i].code = ART_END; + new[i].x1 = 0; + new[i].y1 = 0; + new[i].x2 = 0; + new[i].y2 = 0; + new[i].x3 = 0; + new[i].y3 = 0; + + return new; +} + diff --git a/libart_lgpl/art_bpath.h b/libart_lgpl/art_bpath.h new file mode 100644 index 0000000000..ce73c0b03d --- /dev/null +++ b/libart_lgpl/art_bpath.h @@ -0,0 +1,53 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_BPATH_H__ +#define __ART_BPATH_H__ + +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_point.h> +#include <libart_lgpl/art_pathcode.h> + +/* Basic data structures and constructors for bezier paths */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtBpath ArtBpath; + +struct _ArtBpath { + /*< public >*/ + ArtPathcode code; + double x1; + double y1; + double x2; + double y2; + double x3; + double y3; +}; + +ArtBpath * +art_bpath_affine_transform (const ArtBpath *src, const double matrix[6]); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_BPATH_H__ */ diff --git a/libart_lgpl/art_filterlevel.h b/libart_lgpl/art_filterlevel.h new file mode 100644 index 0000000000..1f4be484b4 --- /dev/null +++ b/libart_lgpl/art_filterlevel.h @@ -0,0 +1,68 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_FILTERLEVEL_H__ +#define __ART_FILTERLEVEL_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + ART_FILTER_NEAREST, + ART_FILTER_TILES, + ART_FILTER_BILINEAR, + ART_FILTER_HYPER +} ArtFilterLevel; + +/* NEAREST is nearest neighbor. It is the fastest and lowest quality. + + TILES is an accurate simulation of the PostScript image operator + without any interpolation enabled; each pixel is rendered as a tiny + parallelogram of solid color, the edges of which are implemented + with antialiasing. It resembles nearest neighbor for enlargement, + and bilinear for reduction. + + BILINEAR is bilinear interpolation. For enlargement, it is + equivalent to point-sampling the ideal bilinear-interpolated + image. For reduction, it is equivalent to laying down small tiles + and integrating over the coverage area. + + HYPER is the highest quality reconstruction function. It is derived + from the hyperbolic filters in Wolberg's "Digital Image Warping," + and is formally defined as the hyperbolic-filter sampling the ideal + hyperbolic-filter interpolated image (the filter is designed to be + idempotent for 1:1 pixel mapping). It is the slowest and highest + quality. + + Note: at this stage of implementation, most filter modes are likely + not to be implemented. + + Note: cubic filtering is missing from this list, because there isn't + much point - hyper is just as fast to implement and slightly better + in quality. + +*/ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_PATHCODE_H__ */ diff --git a/libart_lgpl/art_gray_svp.c b/libart_lgpl/art_gray_svp.c new file mode 100644 index 0000000000..e5d5df9d74 --- /dev/null +++ b/libart_lgpl/art_gray_svp.c @@ -0,0 +1,123 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Render a sorted vector path into a graymap. */ + +#include "config.h" +#include "art_gray_svp.h" + +#include <string.h> /* for memset */ +#include "art_misc.h" + +#include "art_svp.h" +#include "art_svp_render_aa.h" + +typedef struct _ArtGraySVPData ArtGraySVPData; + +struct _ArtGraySVPData { + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_gray_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtGraySVPData *data = (ArtGraySVPData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + int running_sum = start; + int x0, x1; + int k; + +#if 0 + printf ("start = %d", start); + running_sum = start; + for (k = 0; k < n_steps; k++) + { + running_sum += steps[k].delta; + printf (" %d:%d", steps[k].x, running_sum >> 16); + } + printf ("\n"); +#endif + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + memset (linebuf, running_sum >> 16, run_x1 - x0); + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + memset (linebuf + run_x0 - x0, running_sum >> 16, run_x1 - run_x0); + } + running_sum += steps[k].delta; + if (x1 > run_x1) + memset (linebuf + run_x1 - x0, running_sum >> 16, x1 - run_x1); + } + else + { + memset (linebuf, running_sum >> 16, x1 - x0); + } + + data->buf += data->rowstride; +} + +/** + * art_gray_svp_aa: Render the vector path into the bytemap. + * @svp: The SVP to render. + * @x0: The view window's left coord. + * @y0: The view window's top coord. + * @x1: The view window's right coord. + * @y1: The view window's bottom coord. + * @buf: The buffer where the bytemap is stored. + * @rowstride: the rowstride for @buf. + * + * Each pixel gets a value proportional to the area within the pixel + * overlapping the (filled) SVP. Pixel (x, y) is stored at: + * + * @buf[(y - * @y0) * @rowstride + (x - @x0)] + * + * All pixels @x0 <= x < @x1, @y0 <= y < @y1 are generated. A + * stored value of zero is no coverage, and a value of 255 is full + * coverage. The area within the pixel (x, y) is the region covered + * by [x..x+1] and [y..y+1]. + **/ +void +art_gray_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u8 *buf, int rowstride) +{ + ArtGraySVPData data; + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + art_svp_render_aa (svp, x0, y0, x1, y1, art_gray_svp_callback, &data); +} diff --git a/libart_lgpl/art_gray_svp.h b/libart_lgpl/art_gray_svp.h new file mode 100644 index 0000000000..52ab2239e3 --- /dev/null +++ b/libart_lgpl/art_gray_svp.h @@ -0,0 +1,41 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Render a sorted vector path into a graymap. */ + +#ifndef __ART_GRAY_SVP_H__ +#define __ART_GRAY_SVP_H__ + +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_gray_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u8 *buf, int rowstride); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_GRAY_SVP_H__ */ diff --git a/libart_lgpl/art_misc.c b/libart_lgpl/art_misc.c new file mode 100644 index 0000000000..ca3fc5015d --- /dev/null +++ b/libart_lgpl/art_misc.c @@ -0,0 +1,93 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Various utility functions RLL finds useful. */ + +#include "config.h" +#include "art_misc.h" + +#ifdef HAVE_UINSTD_H +#include <unistd.h> +#endif +#include <stdio.h> +#include <stdarg.h> + +/** + * art_die: Print the error message to stderr and exit with a return code of 1. + * @fmt: The printf-style format for the error message. + * + * Used for dealing with severe errors. + **/ +void +art_die (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + exit (1); +} + +/** + * art_warn: Print the warning message to stderr. + * @fmt: The printf-style format for the warning message. + * + * Used for generating warnings. + **/ +void +art_warn (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} + +/** + * art_dprint: Print the debug message to stderr. + * @fmt: The printf-style format for the debug message. + * + * Used for generating debug output. + **/ +void +art_dprint (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} + +void *art_alloc(size_t size) +{ + return malloc(size); +} + +void art_free(void *ptr) +{ + free(ptr); +} + +void *art_realloc(void *ptr, size_t size) +{ + return realloc(ptr, size); +} diff --git a/libart_lgpl/art_misc.h b/libart_lgpl/art_misc.h new file mode 100644 index 0000000000..5bdf74a1b1 --- /dev/null +++ b/libart_lgpl/art_misc.h @@ -0,0 +1,98 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Simple macros to set up storage allocation and basic types for libart + functions. */ + +#ifndef __ART_MISC_H__ +#define __ART_MISC_H__ + +#include <glib.h> +#include <stdlib.h> /* for malloc, etc. */ + +#ifdef __cplusplus +extern "C" { +#endif +void *art_alloc(size_t size); +void art_free(void *ptr); +void *art_realloc(void *ptr, size_t size); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +/* These aren't, strictly speaking, configuration macros, but they're + damn handy to have around, and may be worth playing with for + debugging. */ +#define art_new(type, n) ((type *)art_alloc ((n) * sizeof(type))) + +#define art_renew(p, type, n) ((type *)art_realloc (p, (n) * sizeof(type))) + +/* This one must be used carefully - in particular, p and max should + be variables. They can also be pstruct->el lvalues. */ +#define art_expand(p, type, max) do { if(max) { p = art_renew (p, type, max <<= 1); } else { max = 1; p = art_new(type, 1); } } while (0) + +typedef int art_boolean; +#define ART_FALSE 0 +#define ART_TRUE 1 + +/* define pi */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif /* M_PI */ + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif /* M_SQRT2 */ + +/* Provide macros to feature the GCC function attribute. + */ +#if defined(__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)) +#define ART_GNUC_PRINTF( format_idx, arg_idx ) \ + __attribute__((__format__ (__printf__, format_idx, arg_idx))) +#define ART_GNUC_NORETURN \ + __attribute__((__noreturn__)) +#else /* !__GNUC__ */ +#define ART_GNUC_PRINTF( format_idx, arg_idx ) +#define ART_GNUC_NORETURN +#endif /* !__GNUC__ */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef guint8 art_u8; +typedef guint16 art_u16; +typedef guint32 art_u32; + +void ART_GNUC_NORETURN +art_die (const char *fmt, ...) ART_GNUC_PRINTF (1, 2); + +void +art_warn (const char *fmt, ...) ART_GNUC_PRINTF (1, 2); + +void +art_dprint (const char *fmt, ...) ART_GNUC_PRINTF (1, 2); + +#ifdef __cplusplus +} +#endif + +#define ART_USE_NEW_INTERSECTOR + +#endif /* __ART_MISC_H__ */ diff --git a/libart_lgpl/art_pathcode.h b/libart_lgpl/art_pathcode.h new file mode 100644 index 0000000000..87979ceb8f --- /dev/null +++ b/libart_lgpl/art_pathcode.h @@ -0,0 +1,39 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_PATHCODE_H__ +#define __ART_PATHCODE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + ART_MOVETO, + ART_MOVETO_OPEN, + ART_CURVETO, + ART_LINETO, + ART_END +} ArtPathcode; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_PATHCODE_H__ */ diff --git a/libart_lgpl/art_pixbuf.c b/libart_lgpl/art_pixbuf.c new file mode 100644 index 0000000000..e99375392e --- /dev/null +++ b/libart_lgpl/art_pixbuf.c @@ -0,0 +1,285 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_pixbuf.h" + +#include "art_misc.h" +#include <string.h> + +/** + * art_pixbuf_new_rgb_dnotify: Create a new RGB #ArtPixBuf with explicit destroy notification. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * @dfunc_data: The private data passed to @dfunc. + * @dfunc: The destroy notification function. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * @dfunc is called with @dfunc_data and @pixels as arguments when the + * #ArtPixBuf is destroyed. Using a destroy notification function + * allows a wide range of memory management disciplines for the pixel + * memory. A NULL value for @dfunc is also allowed and means that no + * special action will be taken on destruction. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgb_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc) +{ + ArtPixBuf *pixbuf; + + pixbuf = art_new (ArtPixBuf, 1); + + pixbuf->format = ART_PIX_RGB; + pixbuf->n_channels = 3; + pixbuf->has_alpha = 0; + pixbuf->bits_per_sample = 8; + + pixbuf->pixels = (art_u8 *) pixels; + pixbuf->width = width; + pixbuf->height = height; + pixbuf->rowstride = rowstride; + pixbuf->destroy_data = dfunc_data; + pixbuf->destroy = dfunc; + + return pixbuf; +} + +/** + * art_pixbuf_new_rgba_dnotify: Create a new RGBA #ArtPixBuf with explicit destroy notification. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * @dfunc_data: The private data passed to @dfunc. + * @dfunc: The destroy notification function. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * @dfunc is called with @dfunc_data and @pixels as arguments when the + * #ArtPixBuf is destroyed. Using a destroy notification function + * allows a wide range of memory management disciplines for the pixel + * memory. A NULL value for @dfunc is also allowed and means that no + * special action will be taken on destruction. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgba_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc) +{ + ArtPixBuf *pixbuf; + + pixbuf = art_new (ArtPixBuf, 1); + + pixbuf->format = ART_PIX_RGB; + pixbuf->n_channels = 4; + pixbuf->has_alpha = 1; + pixbuf->bits_per_sample = 8; + + pixbuf->pixels = (art_u8 *) pixels; + pixbuf->width = width; + pixbuf->height = height; + pixbuf->rowstride = rowstride; + pixbuf->destroy_data = dfunc_data; + pixbuf->destroy = dfunc; + + return pixbuf; +} + +/** + * art_pixbuf_new_const_rgb: Create a new RGB #ArtPixBuf with constant pixel data. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * No action is taken when the #ArtPixBuf is destroyed. Thus, this + * function is useful when the pixel data is constant or statically + * allocated. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_const_rgb (const art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgb_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL); +} + +/** + * art_pixbuf_new_const_rgba: Create a new RGBA #ArtPixBuf with constant pixel data. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * No action is taken when the #ArtPixBuf is destroyed. Thus, this + * function is suitable when the pixel data is constant or statically + * allocated. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_const_rgba (const art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgba_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL); +} + +static void +art_pixel_destroy (void *func_data, void *data) +{ + art_free (data); +} + +/** + * art_pixbuf_new_rgb: Create a new RGB #ArtPixBuf. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * The @pixels buffer is freed with art_free() when the #ArtPixBuf is + * destroyed. Thus, this function is suitable when the pixel data is + * allocated with art_alloc(). + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgb (art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgb_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy); +} + +/** + * art_pixbuf_new_rgba: Create a new RGBA #ArtPixBuf. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * The @pixels buffer is freed with art_free() when the #ArtPixBuf is + * destroyed. Thus, this function is suitable when the pixel data is + * allocated with art_alloc(). + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgba (art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgba_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy); +} + +/** + * art_pixbuf_free: Destroy an #ArtPixBuf. + * @pixbuf: The #ArtPixBuf to be destroyed. + * + * Destroys the #ArtPixBuf, calling the destroy notification function + * (if non-NULL) so that the memory for the pixel buffer can be + * properly reclaimed. + **/ +void +art_pixbuf_free (ArtPixBuf *pixbuf) +{ + ArtDestroyNotify destroy = pixbuf->destroy; + void *destroy_data = pixbuf->destroy_data; + art_u8 *pixels = pixbuf->pixels; + + pixbuf->pixels = NULL; + pixbuf->destroy = NULL; + pixbuf->destroy_data = NULL; + + if (destroy) + destroy (destroy_data, pixels); + + art_free (pixbuf); +} + +/** + * art_pixbuf_free_shallow: + * @pixbuf: The #ArtPixBuf to be destroyed. + * + * Destroys the #ArtPixBuf without calling the destroy notification function. + * + * This function is deprecated. Use the _dnotify variants for + * allocation instead. + **/ +void +art_pixbuf_free_shallow (ArtPixBuf *pixbuf) +{ + art_free (pixbuf); +} + +/** + * art_pixbuf_duplicate: Duplicate a pixbuf. + * @pixbuf: The #ArtPixBuf to duplicate. + * + * Duplicates a pixbuf, including duplicating the buffer. + * + * Return value: The duplicated pixbuf. + **/ +ArtPixBuf * +art_pixbuf_duplicate (const ArtPixBuf *pixbuf) +{ + ArtPixBuf *result; + int size; + + result = art_new (ArtPixBuf, 1); + + result->format = pixbuf->format; + result->n_channels = pixbuf->n_channels; + result->has_alpha = pixbuf->has_alpha; + result->bits_per_sample = pixbuf->bits_per_sample; + + size = (pixbuf->height - 1) * pixbuf->rowstride + + pixbuf->width * ((pixbuf->n_channels * pixbuf->bits_per_sample + 7) >> 3); + result->pixels = art_alloc (size); + memcpy (result->pixels, pixbuf->pixels, size); + + result->width = pixbuf->width; + result->height = pixbuf->height; + result->rowstride = pixbuf->rowstride; + result->destroy_data = NULL; + result->destroy = art_pixel_destroy; + + return result; +} diff --git a/libart_lgpl/art_pixbuf.h b/libart_lgpl/art_pixbuf.h new file mode 100644 index 0000000000..31f5620671 --- /dev/null +++ b/libart_lgpl/art_pixbuf.h @@ -0,0 +1,100 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_PIXBUF_H__ +#define __ART_PIXBUF_H__ + +/* A generic data structure for holding a buffer of pixels. One way + to think about this module is as a virtualization over specific + pixel buffer formats. */ + +#include <libart_lgpl/art_misc.h> + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef void (*ArtDestroyNotify) (void *func_data, void *data); + +typedef struct _ArtPixBuf ArtPixBuf; + +typedef enum { + ART_PIX_RGB + /* gray, cmyk, lab, ... ? */ +} ArtPixFormat; + + +/* The pixel buffer consists of width * height pixels, each of which + has n_channels samples. It is stored in simple packed format. */ + +struct _ArtPixBuf { + /*< public >*/ + ArtPixFormat format; + int n_channels; + int has_alpha; + int bits_per_sample; + + art_u8 *pixels; + int width; + int height; + int rowstride; + void *destroy_data; + ArtDestroyNotify destroy; +}; + +/* allocate an ArtPixBuf from art_alloc()ed pixels (automated destruction) */ +ArtPixBuf * +art_pixbuf_new_rgb (art_u8 *pixels, int width, int height, int rowstride); + +ArtPixBuf * +art_pixbuf_new_rgba (art_u8 *pixels, int width, int height, int rowstride); + +/* allocate an ArtPixBuf from constant pixels (no destruction) */ +ArtPixBuf * +art_pixbuf_new_const_rgb (const art_u8 *pixels, int width, int height, int rowstride); + +ArtPixBuf * +art_pixbuf_new_const_rgba (const art_u8 *pixels, int width, int height, int rowstride); + +/* allocate an ArtPixBuf and notify creator upon destruction */ +ArtPixBuf * +art_pixbuf_new_rgb_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc); + +ArtPixBuf * +art_pixbuf_new_rgba_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc); + +/* free an ArtPixBuf with destroy notification */ +void +art_pixbuf_free (ArtPixBuf *pixbuf); + +/* deprecated function, use the _dnotify variants for allocation instead */ +void +art_pixbuf_free_shallow (ArtPixBuf *pixbuf); + +ArtPixBuf * +art_pixbuf_duplicate (const ArtPixBuf *pixbuf); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_point.h b/libart_lgpl/art_point.h new file mode 100644 index 0000000000..1efcda67aa --- /dev/null +++ b/libart_lgpl/art_point.h @@ -0,0 +1,38 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_POINT_H__ +#define __ART_POINT_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtPoint ArtPoint; + +struct _ArtPoint { + /*< public >*/ + double x, y; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_POINT_H__ */ diff --git a/libart_lgpl/art_rect.c b/libart_lgpl/art_rect.c new file mode 100644 index 0000000000..c9dd5b3702 --- /dev/null +++ b/libart_lgpl/art_rect.c @@ -0,0 +1,215 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect.h" + +#include <math.h> + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/* rectangle primitives stolen from gzilla */ + +/** + * art_irect_copy: Make a copy of an integer rectangle. + * @dest: Where the copy is stored. + * @src: The source rectangle. + * + * Copies the rectangle. + **/ +void +art_irect_copy (ArtIRect *dest, const ArtIRect *src) { + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +/** + * art_irect_union: Find union of two integer rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the smallest rectangle that includes @src1 and @src2. + **/ +void +art_irect_union (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) { + if (art_irect_empty (src1)) { + art_irect_copy (dest, src2); + } else if (art_irect_empty (src2)) { + art_irect_copy (dest, src1); + } else { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +/** + * art_irect_intersection: Find intersection of two integer rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the intersection of @src1 and @src2. + **/ +void +art_irect_intersect (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) { + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * art_irect_empty: Determine whether integer rectangle is empty. + * @src: The source rectangle. + * + * Return value: TRUE if @src is an empty rectangle, FALSE otherwise. + **/ +int +art_irect_empty (const ArtIRect *src) { + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +#if 0 +gboolean irect_point_inside (ArtIRect *rect, GzwPoint *point) { + return (point->x >= rect->x0 && point->y >= rect->y0 && + point->x < rect->x1 && point->y < rect->y1); +} +#endif + +/** + * art_drect_copy: Make a copy of a rectangle. + * @dest: Where the copy is stored. + * @src: The source rectangle. + * + * Copies the rectangle. + **/ +void +art_drect_copy (ArtDRect *dest, const ArtDRect *src) { + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +/** + * art_drect_union: Find union of two rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the smallest rectangle that includes @src1 and @src2. + **/ +void +art_drect_union (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) { + if (art_drect_empty (src1)) { + art_drect_copy (dest, src2); + } else if (art_drect_empty (src2)) { + art_drect_copy (dest, src1); + } else { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +/** + * art_drect_intersection: Find intersection of two rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the intersection of @src1 and @src2. + **/ +void +art_drect_intersect (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) { + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * art_irect_empty: Determine whether rectangle is empty. + * @src: The source rectangle. + * + * Return value: TRUE if @src is an empty rectangle, FALSE otherwise. + **/ +int +art_drect_empty (const ArtDRect *src) { + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +/** + * art_drect_affine_transform: Affine transform rectangle. + * @dst: Where to store the result. + * @src: The source rectangle. + * @matrix: The affine transformation. + * + * Find the smallest rectangle enclosing the affine transformed @src. + * The result is exactly the affine transformation of @src when + * @matrix specifies a rectilinear affine transformation, otherwise it + * is a conservative approximation. + **/ +void +art_drect_affine_transform (ArtDRect *dst, const ArtDRect *src, const double matrix[6]) +{ + double x00, y00, x10, y10; + double x01, y01, x11, y11; + + x00 = src->x0 * matrix[0] + src->y0 * matrix[2] + matrix[4]; + y00 = src->x0 * matrix[1] + src->y0 * matrix[3] + matrix[5]; + x10 = src->x1 * matrix[0] + src->y0 * matrix[2] + matrix[4]; + y10 = src->x1 * matrix[1] + src->y0 * matrix[3] + matrix[5]; + x01 = src->x0 * matrix[0] + src->y1 * matrix[2] + matrix[4]; + y01 = src->x0 * matrix[1] + src->y1 * matrix[3] + matrix[5]; + x11 = src->x1 * matrix[0] + src->y1 * matrix[2] + matrix[4]; + y11 = src->x1 * matrix[1] + src->y1 * matrix[3] + matrix[5]; + dst->x0 = MIN (MIN (x00, x10), MIN (x01, x11)); + dst->y0 = MIN (MIN (y00, y10), MIN (y01, y11)); + dst->x1 = MAX (MAX (x00, x10), MAX (x01, x11)); + dst->y1 = MAX (MAX (y00, y10), MAX (y01, y11)); +} + +/** + * art_drect_to_irect: Convert rectangle to integer rectangle. + * @dst: Where to store resulting integer rectangle. + * @src: The source rectangle. + * + * Find the smallest integer rectangle that encloses @src. + **/ +void +art_drect_to_irect (ArtIRect *dst, ArtDRect *src) +{ + dst->x0 = floor (src->x0); + dst->y0 = floor (src->y0); + dst->x1 = ceil (src->x1); + dst->y1 = ceil (src->y1); +} diff --git a/libart_lgpl/art_rect.h b/libart_lgpl/art_rect.h new file mode 100644 index 0000000000..088079f712 --- /dev/null +++ b/libart_lgpl/art_rect.h @@ -0,0 +1,78 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RECT_H__ +#define __ART_RECT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ArtDRect ArtDRect; +typedef struct _ArtIRect ArtIRect; + +struct _ArtDRect { + /*< public >*/ + double x0, y0, x1, y1; +}; + +struct _ArtIRect { + /*< public >*/ + int x0, y0, x1, y1; +}; + +/* Make a copy of the rectangle. */ +void art_irect_copy (ArtIRect *dest, const ArtIRect *src); + +/* Find the smallest rectangle that includes both source rectangles. */ +void art_irect_union (ArtIRect *dest, + const ArtIRect *src1, const ArtIRect *src2); + +/* Return the intersection of the two rectangles */ +void art_irect_intersect (ArtIRect *dest, + const ArtIRect *src1, const ArtIRect *src2); + +/* Return true if the rectangle is empty. */ +int art_irect_empty (const ArtIRect *src); + +/* Make a copy of the rectangle. */ +void art_drect_copy (ArtDRect *dest, const ArtDRect *src); + +/* Find the smallest rectangle that includes both source rectangles. */ +void art_drect_union (ArtDRect *dest, + const ArtDRect *src1, const ArtDRect *src2); + +/* Return the intersection of the two rectangles */ +void art_drect_intersect (ArtDRect *dest, + const ArtDRect *src1, const ArtDRect *src2); + +/* Return true if the rectangle is empty. */ +int art_drect_empty (const ArtDRect *src); + +void +art_drect_affine_transform (ArtDRect *dst, const ArtDRect *src, + const double matrix[6]); + +void art_drect_to_irect (ArtIRect *dst, ArtDRect *src); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rect_svp.c b/libart_lgpl/art_rect_svp.c new file mode 100644 index 0000000000..d9819963a9 --- /dev/null +++ b/libart_lgpl/art_rect_svp.c @@ -0,0 +1,82 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect_svp.h" + +#include "art_misc.h" +#include "art_svp.h" +#include "art_rect.h" + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/** + * art_drect_svp: Find the bounding box of a sorted vector path. + * @bbox: Where to store the bounding box. + * @svp: The SVP. + * + * Finds the bounding box of the SVP. + **/ +void +art_drect_svp (ArtDRect *bbox, const ArtSVP *svp) +{ + int i; + + if (svp->n_segs == 0) + { + bbox->x0 = 0; + bbox->y0 = 0; + bbox->x1 = 0; + bbox->y1 = 0; + return; + } + + art_drect_copy (bbox, &svp->segs[0].bbox); + + for (i = 1; i < svp->n_segs; i++) + { + bbox->x0 = MIN (bbox->x0, svp->segs[i].bbox.x0); + bbox->y0 = MIN (bbox->y0, svp->segs[i].bbox.y0); + bbox->x1 = MAX (bbox->x1, svp->segs[i].bbox.x1); + bbox->y1 = MAX (bbox->y1, svp->segs[i].bbox.y1); + } +} + +/** + * art_drect_svp_union: Compute the bounding box of the svp and union it in to the existing bounding box. + * @bbox: Initial boundin box and where to store the bounding box. + * @svp: The SVP. + * + * Finds the bounding box of the SVP, computing its union with an + * existing bbox. + **/ +void +art_drect_svp_union (ArtDRect *bbox, const ArtSVP *svp) +{ + ArtDRect svp_bbox; + + art_drect_svp (&svp_bbox, svp); + art_drect_union (bbox, bbox, &svp_bbox); +} diff --git a/libart_lgpl/art_rect_svp.h b/libart_lgpl/art_rect_svp.h new file mode 100644 index 0000000000..496826decf --- /dev/null +++ b/libart_lgpl/art_rect_svp.h @@ -0,0 +1,43 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RECT_SVP_H__ +#define __ART_RECT_SVP_H__ + +/* Find the bounding box of a sorted vector path. */ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_drect_svp (ArtDRect *bbox, const ArtSVP *svp); + +/* Compute the bounding box of the svp and union it in to the + existing bounding box. */ +void +art_drect_svp_union (ArtDRect *bbox, const ArtSVP *svp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RECT_SVP_H__ */ diff --git a/libart_lgpl/art_rect_uta.c b/libart_lgpl/art_rect_uta.c new file mode 100644 index 0000000000..cd002f81af --- /dev/null +++ b/libart_lgpl/art_rect_uta.c @@ -0,0 +1,134 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect_uta.h" + +/* Functions to decompose a microtile array into a list of rectangles. */ + +/** + * art_rect_list_from_uta: Decompose uta into list of rectangles. + * @uta: The source uta. + * @max_width: The maximum width of the resulting rectangles. + * @max_height: The maximum height of the resulting rectangles. + * @p_nrects: Where to store the number of returned rectangles. + * + * Allocates a new list of rectangles, sets *@p_nrects to the number + * in the list. This list should be freed with art_free(). + * + * Each rectangle bounded in size by (@max_width, @max_height). + * However, these bounds must be at least the size of one tile. + * + * This routine provides a precise implementation, i.e. the rectangles + * cover exactly the same area as the uta. It is thus appropriate in + * cases where the overhead per rectangle is small compared with the + * cost of filling in extra pixels. + * + * Return value: An array containing the resulting rectangles. + **/ +ArtIRect * +art_rect_list_from_uta (ArtUta *uta, int max_width, int max_height, + int *p_nrects) +{ + ArtIRect *rects; + int n_rects, n_rects_max; + int x, y; + int width, height; + int ix; + int left_ix; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int x0, y0, x1, y1; + int *glom; + int glom_rect; + + n_rects = 0; + n_rects_max = 1; + rects = art_new (ArtIRect, n_rects_max); + + width = uta->width; + height = uta->height; + utiles = uta->utiles; + + glom = art_new (int, width * height); + for (ix = 0; ix < width * height; ix++) + glom[ix] = -1; + + ix = 0; + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + { + bb = utiles[ix]; + if (bb) + { + x0 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X0(bb); + y0 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y0(bb); + y1 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y1(bb); + + left_ix = ix; + /* now try to extend to the right */ + while (x != width - 1 && + ART_UTA_BBOX_X1(bb) == ART_UTILE_SIZE && + (((bb & 0xffffff) ^ utiles[ix + 1]) & 0xffff00ff) == 0 && + (((uta->x0 + x + 1) << ART_UTILE_SHIFT) + + ART_UTA_BBOX_X1(utiles[ix + 1]) - + x0) <= max_width) + { + bb = utiles[ix + 1]; + ix++; + x++; + } + x1 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X1(bb); + + + /* if rectangle nonempty */ + if ((x1 ^ x0) | (y1 ^ y0)) + { + /* try to glom onto an existing rectangle */ + glom_rect = glom[left_ix]; + if (glom_rect != -1 && + x0 == rects[glom_rect].x0 && + x1 == rects[glom_rect].x1 && + y0 == rects[glom_rect].y1 && + y1 - rects[glom_rect].y0 <= max_height) + { + rects[glom_rect].y1 = y1; + } + else + { + if (n_rects == n_rects_max) + art_expand (rects, ArtIRect, n_rects_max); + rects[n_rects].x0 = x0; + rects[n_rects].y0 = y0; + rects[n_rects].x1 = x1; + rects[n_rects].y1 = y1; + glom_rect = n_rects; + n_rects++; + } + if (y != height - 1) + glom[left_ix + width] = glom_rect; + } + } + ix++; + } + + art_free (glom); + *p_nrects = n_rects; + return rects; +} diff --git a/libart_lgpl/art_rect_uta.h b/libart_lgpl/art_rect_uta.h new file mode 100644 index 0000000000..39a1880782 --- /dev/null +++ b/libart_lgpl/art_rect_uta.h @@ -0,0 +1,38 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RECT_UTA_H__ +#define __ART_RECT_UTA_H__ + +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_uta.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtIRect * +art_rect_list_from_uta (ArtUta *uta, int max_width, int max_height, + int *p_nrects); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RECT_UTA_H__ */ diff --git a/libart_lgpl/art_render.c b/libart_lgpl/art_render.c new file mode 100644 index 0000000000..65b344cc56 --- /dev/null +++ b/libart_lgpl/art_render.c @@ -0,0 +1,1383 @@ +/* + * art_render.c: Modular rendering architecture. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_render.h" + +#include "art_rgb.h" + +typedef struct _ArtRenderPriv ArtRenderPriv; + +struct _ArtRenderPriv { + ArtRender super; + + ArtImageSource *image_source; + + int n_mask_source; + ArtMaskSource **mask_source; + + int n_callbacks; + ArtRenderCallback **callbacks; +}; + +ArtRender * +art_render_new (int x0, int y0, int x1, int y1, + art_u8 *pixels, int rowstride, + int n_chan, int depth, ArtAlphaType alpha_type, + ArtAlphaGamma *alphagamma) +{ + ArtRenderPriv *priv; + ArtRender *result; + + priv = art_new (ArtRenderPriv, 1); + result = &priv->super; + + if (n_chan > ART_MAX_CHAN) + { + art_warn ("art_render_new: n_chan = %d, exceeds %d max\n", + n_chan, ART_MAX_CHAN); + return NULL; + } + if (depth > ART_MAX_DEPTH) + { + art_warn ("art_render_new: depth = %d, exceeds %d max\n", + depth, ART_MAX_DEPTH); + return NULL; + } + if (x0 >= x1) + { + art_warn ("art_render_new: x0 >= x1 (x0 = %d, x1 = %d)\n", x0, x1); + return NULL; + } + result->x0 = x0; + result->y0 = y0; + result->x1 = x1; + result->y1 = y1; + result->pixels = pixels; + result->rowstride = rowstride; + result->n_chan = n_chan; + result->depth = depth; + result->alpha_type = alpha_type; + + result->clear = ART_FALSE; + result->opacity = 0x10000; + result->compositing_mode = ART_COMPOSITE_NORMAL; + result->alphagamma = alphagamma; + + result->alpha_buf = NULL; + result->image_buf = NULL; + + result->run = NULL; + result->span_x = NULL; + + result->need_span = ART_FALSE; + + priv->image_source = NULL; + + priv->n_mask_source = 0; + priv->mask_source = NULL; + + return result; +} + +/* todo on clear routines: I haven't really figured out what to do + with clearing the alpha channel. It _should_ be possible to clear + to an arbitrary RGBA color. */ + +/** + * art_render_clear: Set clear color. + * @clear_color: Color with which to clear dest. + * + * Sets clear color, equivalent to actually clearing the destination + * buffer before rendering. This is the most general form. + **/ +void +art_render_clear (ArtRender *render, const ArtPixMaxDepth *clear_color) +{ + int i; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + + render->clear = ART_TRUE; + for (i = 0; i < n_ch; i++) + render->clear_color[i] = clear_color[i]; +} + +/** + * art_render_clear_rgb: Set clear color, given in RGB format. + * @clear_rgb: Clear color, in 0xRRGGBB format. + * + * Sets clear color, equivalent to actually clearing the destination + * buffer before rendering. + **/ +void +art_render_clear_rgb (ArtRender *render, art_u32 clear_rgb) +{ + if (render->n_chan != 3) + art_warn ("art_render_clear_rgb: called on render with %d channels, only works with 3\n", + render->n_chan); + else + { + int r, g, b; + + render->clear = ART_TRUE; + r = clear_rgb >> 16; + g = (clear_rgb >> 8) & 0xff; + b = clear_rgb & 0xff; + render->clear_color[0] = ART_PIX_MAX_FROM_8(r); + render->clear_color[1] = ART_PIX_MAX_FROM_8(g); + render->clear_color[2] = ART_PIX_MAX_FROM_8(b); + } +} + +static void +art_render_nop_done (ArtRenderCallback *self, ArtRender *render) +{ +} + +static void +art_render_clear_render_rgb8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + art_u8 r, g, b; + ArtPixMaxDepth color_max; + + color_max = render->clear_color[0]; + r = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[1]; + g = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[2]; + b = ART_PIX_8_FROM_MAX (color_max); + + art_rgb_fill_run (dest, r, g, b, width); +} + +static void +art_render_clear_render_8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + int i, j; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + int ix; + art_u8 color[ART_MAX_CHAN + 1]; + + for (j = 0; j < n_ch; j++) + { + ArtPixMaxDepth color_max = render->clear_color[j]; + color[j] = ART_PIX_8_FROM_MAX (color_max); + } + + ix = 0; + for (i = 0; i < width; i++) + for (j = 0; j < n_ch; j++) + dest[ix++] = color[j]; +} + +const ArtRenderCallback art_render_clear_rgb8_obj = +{ + art_render_clear_render_rgb8, + art_render_nop_done +}; + +const ArtRenderCallback art_render_clear_8_obj = +{ + art_render_clear_render_8, + art_render_nop_done +}; + +#if ART_MAX_DEPTH >= 16 + +static void +art_render_clear_render_16 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + int i, j; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + int ix; + art_u16 *dest_16 = (art_u16 *)dest; + art_u8 color[ART_MAX_CHAN + 1]; + + for (j = 0; j < n_ch; j++) + { + int color_16 = render->clear_color[j]; + color[j] = color_16; + } + + ix = 0; + for (i = 0; i < width; i++) + for (j = 0; j < n_ch; j++) + dest_16[ix++] = color[j]; +} + +const ArtRenderCallback art_render_clear_16_obj = +{ + art_render_clear_render_16, + art_render_nop_done +}; + +#endif /* ART_MAX_DEPTH >= 16 */ + +/* todo: inline */ +static ArtRenderCallback * +art_render_choose_clear_callback (ArtRender *render) +{ + ArtRenderCallback *clear_callback; + + if (render->depth == 8) + { + if (render->n_chan == 3 && + render->alpha_type == ART_ALPHA_NONE) + clear_callback = (ArtRenderCallback *)&art_render_clear_rgb8_obj; + else + clear_callback = (ArtRenderCallback *)&art_render_clear_8_obj; + } +#if ART_MAX_DEPTH >= 16 + else if (render->depth == 16) + clear_callback = (ArtRenderCallback *)&art_render_clear_16_obj; +#endif + else + { + art_die ("art_render_choose_clear_callback: inconsistent render->depth = %d\n", + render->depth); + } + return clear_callback; +} + +#if 0 +/* todo: get around to writing this */ +static void +art_render_composite_render_noa_8_norm (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + +} +#endif + +/* This is the most general form of the function. It is slow but + (hopefully) correct. Actually, I'm still worried about roundoff + errors in the premul case - it seems to me that an off-by-one could + lead to overflow. */ +static void +art_render_composite (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + art_u32 depth = render->depth; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *alpha_buf = render->alpha_buf; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + art_u32 alpha; + int image_ix; + art_u16 src[ART_MAX_CHAN + 1]; + art_u16 dst[ART_MAX_CHAN + 1]; + int n_chan = render->n_chan; + ArtAlphaType alpha_type = render->alpha_type; + int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE); + int dst_pixstride = n_ch * (depth >> 3); + int buf_depth = render->buf_depth; + ArtAlphaType buf_alpha = render->buf_alpha; + int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE); + int buf_pixstride = buf_n_ch * (buf_depth >> 3); + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x8100) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * buf_pixstride; + dstptr = dest + (run_x0 - x0) * dst_pixstride; + for (x = run_x0; x < run_x1; x++) + { + if (alpha_buf) + { + if (depth == 8) + { + tmp = run_alpha * alpha_buf[x - x0] + 0x80; + /* range 0x80 .. 0xff0080 */ + alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)alpha_buf)[x - x0]; + tmp = (run_alpha * tmp + 0x8000) >> 8; + /* range 0x80 .. 0xffff80 */ + alpha = (tmp + (tmp >> 16)) >> 8; + } + } + else + alpha = run_alpha; + /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */ + + /* convert (src pixel * alpha) to premul alpha form, + store in src as 0..0xffff range */ + if (buf_alpha == ART_ALPHA_NONE) + { + src_alpha = alpha; + src_mul = src_alpha; + } + else + { + if (buf_depth == 8) + { + tmp = alpha * bufptr[n_chan] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)bufptr)[n_chan]; + tmp = (alpha * tmp + 0x8000) >> 8; + /* range 0x80 .. 0xffff80 */ + src_alpha = (tmp + (tmp >> 16)) >> 8; + } + if (buf_alpha == ART_ALPHA_SEPARATE) + src_mul = src_alpha; + else /* buf_alpha == (ART_ALPHA_PREMUL) */ + src_mul = alpha; + } + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + if (buf_depth == 8) + { + src_mul *= 0x101; + for (j = 0; j < n_chan; j++) + src[j] = (bufptr[j] * src_mul + 0x8000) >> 16; + } + else if (buf_depth == 16) + { + for (j = 0; j < n_chan; j++) + src[j] = (((art_u16 *)bufptr)[j] * src_mul + 0x8000) >> 16; + } + bufptr += buf_pixstride; + + /* src[0..n_chan - 1] (range 0..0xffff) and src_alpha (range + 0..0x10000) now contain the source pixel with + premultiplied alpha */ + + /* convert dst pixel to premul alpha form, + store in dst as 0..0xffff range */ + if (alpha_type == ART_ALPHA_NONE) + { + dst_alpha = 0x10000; + dst_mul = dst_alpha; + } + else + { + if (depth == 8) + { + tmp = dstptr[n_chan]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)dstptr)[n_chan]; + dst_alpha = (tmp + (tmp >> 15)); + } + if (alpha_type == ART_ALPHA_SEPARATE) + dst_mul = dst_alpha; + else /* (alpha_type == ART_ALPHA_PREMUL) */ + dst_mul = 0x10000; + } + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + if (depth == 8) + { + dst_mul *= 0x101; + for (j = 0; j < n_chan; j++) + dst[j] = (dstptr[j] * dst_mul + 0x8000) >> 16; + } + else if (buf_depth == 16) + { + for (j = 0; j < n_chan; j++) + dst[j] = (((art_u16 *)dstptr)[j] * dst_mul + 0x8000) >> 16; + } + + /* do the compositing, dst = (src over dst) */ + for (j = 0; j < n_chan; j++) + { + art_u32 srcv, dstv; + art_u32 tmp; + + srcv = src[j]; + dstv = dst[j]; + tmp = ((dstv * (0x10000 - src_alpha) + 0x8000) >> 16) + srcv; + tmp -= tmp >> 16; + dst[j] = tmp; + } + + if (alpha_type == ART_ALPHA_NONE) + { + if (depth == 8) + dst_mul = 0xff; + else /* (depth == 16) */ + dst_mul = 0xffff; + } + else + { + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0) + { + if (depth == 8) + dst_mul = 0xff; + else /* (depth == 16) */ + dst_mul = 0xffff; + } + else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */ + { + if (depth == 8) + dst_mul = 0xff0000 / dst_alpha; + else /* (depth == 16) */ + dst_mul = 0xffff0000 / dst_alpha; + } + } + if (depth == 8) + { + for (j = 0; j < n_chan; j++) + dstptr[j] = (dst[j] * dst_mul + 0x8000) >> 16; + if (alpha_type != ART_ALPHA_NONE) + dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16; + } + else if (depth == 16) + { + for (j = 0; j < n_chan; j++) + ((art_u16 *)dstptr)[j] = (dst[j] * dst_mul + 0x8000) >> 16; + if (alpha_type != ART_ALPHA_NONE) + ((art_u16 *)dstptr)[n_chan] = (dst_alpha * 0xffff + 0x8000) >> 16; + } + dstptr += dst_pixstride; + } + } +} + +const ArtRenderCallback art_render_composite_obj = +{ + art_render_composite, + art_render_nop_done +}; + +static void +art_render_composite_8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *alpha_buf = render->alpha_buf; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + art_u32 alpha; + int image_ix; + int n_chan = render->n_chan; + ArtAlphaType alpha_type = render->alpha_type; + int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE); + int dst_pixstride = n_ch; + ArtAlphaType buf_alpha = render->buf_alpha; + int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE); + int buf_pixstride = buf_n_ch; + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * buf_pixstride; + dstptr = dest + (run_x0 - x0) * dst_pixstride; + for (x = run_x0; x < run_x1; x++) + { + if (alpha_buf) + { + tmp = run_alpha * alpha_buf[x - x0] + 0x80; + /* range 0x80 .. 0xff0080 */ + alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else + alpha = run_alpha; + /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */ + + /* convert (src pixel * alpha) to premul alpha form, + store in src as 0..0xffff range */ + if (buf_alpha == ART_ALPHA_NONE) + { + src_alpha = alpha; + src_mul = src_alpha; + } + else + { + tmp = alpha * bufptr[n_chan] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + + if (buf_alpha == ART_ALPHA_SEPARATE) + src_mul = src_alpha; + else /* buf_alpha == (ART_ALPHA_PREMUL) */ + src_mul = alpha; + } + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + src_mul *= 0x101; + + if (alpha_type == ART_ALPHA_NONE) + { + dst_alpha = 0x10000; + dst_mul = dst_alpha; + } + else + { + tmp = dstptr[n_chan]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + if (alpha_type == ART_ALPHA_SEPARATE) + dst_mul = dst_alpha; + else /* (alpha_type == ART_ALPHA_PREMUL) */ + dst_mul = 0x10000; + } + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + if (alpha_type == ART_ALPHA_NONE) + { + dst_save_mul = 0xff; + } + else + { + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0) + { + dst_save_mul = 0xff; + } + else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */ + { + dst_save_mul = 0xff0000 / dst_alpha; + } + } + + for (j = 0; j < n_chan; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + if (alpha_type != ART_ALPHA_NONE) + dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += buf_pixstride; + dstptr += dst_pixstride; + } + } +} + +const ArtRenderCallback art_render_composite_8_obj = +{ + art_render_composite_8, + art_render_nop_done +}; + + +/* Assumes: + * alpha_buf is NULL + * buf_alpha = ART_ALPHA_NONE (source) + * alpha_type = ART_ALPHA_SEPARATE (dest) + * n_chan = 3; + */ +static void +art_render_composite_8_opt1 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + int image_ix; + art_u8 *bufptr; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * 3; + dstptr = dest + (run_x0 - x0) * 4; + if (run_alpha == 0x10000) + { + for (x = run_x0; x < run_x1; x++) + { + *dstptr++ = *bufptr++; + *dstptr++ = *bufptr++; + *dstptr++ = *bufptr++; + *dstptr++ = 0xff; + } + } + else + { + for (x = run_x0; x < run_x1; x++) + { + src_mul = run_alpha * 0x101; + + tmp = dstptr[3]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + dst_mul = dst_alpha; + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + dst_alpha += ((((0x10000 - dst_alpha) * run_alpha) >> 8) + 0x80) >> 8; + if (dst_alpha == 0) + dst_save_mul = 0xff; + else /* (dst_alpha != 0) */ + dst_save_mul = 0xff0000 / dst_alpha; + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - run_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 3; + dstptr += 4; + } + } + } +} + + +const ArtRenderCallback art_render_composite_8_opt1_obj = +{ + art_render_composite_8_opt1, + art_render_nop_done +}; + +/* Assumes: + * alpha_buf is NULL + * buf_alpha = ART_ALPHA_PREMUL (source) + * alpha_type = ART_ALPHA_SEPARATE (dest) + * n_chan = 3; + */ +static void +art_render_composite_8_opt2 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + int image_ix; + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * 4; + dstptr = dest + (run_x0 - x0) * 4; + if (run_alpha == 0x10000) + { + for (x = run_x0; x < run_x1; x++) + { + src_alpha = (bufptr[3] << 8) + bufptr[3] + (bufptr[3] >> 7); + /* src_alpha is the (alpha of the source pixel), + range 0..0x10000 */ + + dst_alpha = (dstptr[3] << 8) + dstptr[3] + (dstptr[3] >> 7); + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul = dst_alpha*0x101; + + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + + if (dst_alpha == 0) + dst_save_mul = 0xff; + else /* dst_alpha != 0) */ + dst_save_mul = 0xff0000 / dst_alpha; + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] << 8) | bufptr[j]; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 4; + dstptr += 4; + } + } + else + { + for (x = run_x0; x < run_x1; x++) + { + tmp = run_alpha * bufptr[3] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + src_mul = run_alpha * 0x101; + + tmp = dstptr[3]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + dst_mul = dst_alpha; + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + + if (dst_alpha == 0) + { + dst_save_mul = 0xff; + } + else /* dst_alpha != 0) */ + { + dst_save_mul = 0xff0000 / dst_alpha; + } + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 4; + dstptr += 4; + } + } + } +} + +const ArtRenderCallback art_render_composite_8_opt2_obj = +{ + art_render_composite_8_opt2, + art_render_nop_done +}; + + +/* todo: inline */ +static ArtRenderCallback * +art_render_choose_compositing_callback (ArtRender *render) +{ + if (render->depth == 8 && render->buf_depth == 8) + { + if (render->n_chan == 3 && + render->alpha_buf == NULL && + render->alpha_type == ART_ALPHA_SEPARATE) + { + if (render->buf_alpha == ART_ALPHA_NONE) + return (ArtRenderCallback *)&art_render_composite_8_opt1_obj; + else if (render->buf_alpha == ART_ALPHA_PREMUL) + return (ArtRenderCallback *)&art_render_composite_8_opt2_obj; + } + + return (ArtRenderCallback *)&art_render_composite_8_obj; + } + return (ArtRenderCallback *)&art_render_composite_obj; +} + +/** + * art_render_invoke_callbacks: Invoke the callbacks in the render object. + * @render: The render object. + * @y: The current Y coordinate value. + * + * Invokes the callbacks of the render object in the appropriate + * order. Drivers should call this routine once per scanline. + * + * todo: should management of dest devolve to this routine? very + * plausibly yes. + **/ +void +art_render_invoke_callbacks (ArtRender *render, art_u8 *dest, int y) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int i; + + for (i = 0; i < priv->n_callbacks; i++) + { + ArtRenderCallback *callback; + + callback = priv->callbacks[i]; + callback->render (callback, render, dest, y); + } +} + +/** + * art_render_invoke: Perform the requested rendering task. + * @render: The render object. + * + * Invokes the renderer and all sources associated with it, to perform + * the requested rendering task. + **/ +void +art_render_invoke (ArtRender *render) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int width; + int best_driver, best_score; + int i; + int n_callbacks, n_callbacks_max; + ArtImageSource *image_source; + ArtImageSourceFlags image_flags; + int buf_depth; + ArtAlphaType buf_alpha; + art_boolean first = ART_TRUE; + + if (render == NULL) + { + art_warn ("art_render_invoke: called with render == NULL\n"); + return; + } + if (priv->image_source == NULL) + { + art_warn ("art_render_invoke: no image source given\n"); + return; + } + + width = render->x1 - render->x0; + + render->run = art_new (ArtRenderMaskRun, width + 1); + + /* Elect a mask source as driver. */ + best_driver = -1; + best_score = 0; + for (i = 0; i < priv->n_mask_source; i++) + { + int score; + ArtMaskSource *mask_source; + + mask_source = priv->mask_source[i]; + score = mask_source->can_drive (mask_source, render); + if (score > best_score) + { + best_score = score; + best_driver = i; + } + } + + /* Allocate alpha buffer if needed. */ + if (priv->n_mask_source > 1 || + (priv->n_mask_source == 1 && best_driver < 0)) + { + render->alpha_buf = art_new (art_u8, (width * render->depth) >> 3); + } + + /* Negotiate image rendering and compositing. */ + image_source = priv->image_source; + image_source->negotiate (image_source, render, &image_flags, &buf_depth, + &buf_alpha); + + /* Build callback list. */ + n_callbacks_max = priv->n_mask_source + 3; + priv->callbacks = art_new (ArtRenderCallback *, n_callbacks_max); + n_callbacks = 0; + for (i = 0; i < priv->n_mask_source; i++) + if (i != best_driver) + { + ArtMaskSource *mask_source = priv->mask_source[i]; + + mask_source->prepare (mask_source, render, first); + first = ART_FALSE; + priv->callbacks[n_callbacks++] = &mask_source->super; + } + + if (render->clear && !(image_flags & ART_IMAGE_SOURCE_CAN_CLEAR)) + priv->callbacks[n_callbacks++] = + art_render_choose_clear_callback (render); + + priv->callbacks[n_callbacks++] = &image_source->super; + + /* Allocate image buffer and add compositing callback if needed. */ + if (!(image_flags & ART_IMAGE_SOURCE_CAN_COMPOSITE)) + { + int bytespp = ((render->n_chan + (buf_alpha != ART_ALPHA_NONE)) * + buf_depth) >> 3; + render->buf_depth = buf_depth; + render->buf_alpha = buf_alpha; + render->image_buf = art_new (art_u8, width * bytespp); + priv->callbacks[n_callbacks++] = + art_render_choose_compositing_callback (render); + } + + priv->n_callbacks = n_callbacks; + + if (render->need_span) + render->span_x = art_new (int, width + 1); + + /* Invoke the driver */ + if (best_driver >= 0) + { + ArtMaskSource *driver; + + driver = priv->mask_source[best_driver]; + driver->invoke_driver (driver, render); + } + else + { + art_u8 *dest_ptr = render->pixels; + int y; + + /* Dummy driver */ + render->n_run = 2; + render->run[0].x = render->x0; + render->run[0].alpha = 0x8000 + 0xff * render->opacity; + render->run[1].x = render->x1; + render->run[1].alpha = 0x8000; + if (render->need_span) + { + render->n_span = 2; + render->span_x[0] = render->x0; + render->span_x[1] = render->x1; + } + for (y = render->y0; y < render->y1; y++) + { + art_render_invoke_callbacks (render, dest_ptr, y); + dest_ptr += render->rowstride; + } + } + + if (priv->mask_source != NULL) + art_free (priv->mask_source); + + /* clean up callbacks */ + for (i = 0; i < priv->n_callbacks; i++) + { + ArtRenderCallback *callback; + + callback = priv->callbacks[i]; + callback->done (callback, render); + } + + /* Tear down object */ + if (render->alpha_buf != NULL) + art_free (render->alpha_buf); + if (render->image_buf != NULL) + art_free (render->image_buf); + art_free (render->run); + if (render->span_x != NULL) + art_free (render->span_x); + art_free (priv->callbacks); + art_free (render); +} + +/** + * art_render_mask_solid: Add a solid translucent mask. + * @render: The render object. + * @opacity: Opacity in [0..0x10000] form. + * + * Adds a translucent mask to the rendering object. + **/ +void +art_render_mask_solid (ArtRender *render, int opacity) +{ + art_u32 old_opacity = render->opacity; + art_u32 new_opacity_tmp; + + if (opacity == 0x10000) + /* avoid potential overflow */ + return; + new_opacity_tmp = old_opacity * (art_u32)opacity + 0x8000; + render->opacity = new_opacity_tmp >> 16; +} + +/** + * art_render_add_mask_source: Add a mask source to the render object. + * @render: Render object. + * @mask_source: Mask source to add. + * + * This routine adds a mask source to the render object. In general, + * client api's for adding mask sources should just take a render object, + * then the mask source creation function should call this function. + * Clients should never have to call this function directly, unless of + * course they're creating custom mask sources. + **/ +void +art_render_add_mask_source (ArtRender *render, ArtMaskSource *mask_source) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int n_mask_source = priv->n_mask_source++; + + if (n_mask_source == 0) + priv->mask_source = art_new (ArtMaskSource *, 1); + /* This predicate is true iff n_mask_source is a power of two */ + else if (!(n_mask_source & (n_mask_source - 1))) + priv->mask_source = art_renew (priv->mask_source, ArtMaskSource *, + n_mask_source << 1); + + priv->mask_source[n_mask_source] = mask_source; +} + +/** + * art_render_add_image_source: Add a mask source to the render object. + * @render: Render object. + * @image_source: Image source to add. + * + * This routine adds an image source to the render object. In general, + * client api's for adding image sources should just take a render + * object, then the mask source creation function should call this + * function. Clients should never have to call this function + * directly, unless of course they're creating custom image sources. + **/ +void +art_render_add_image_source (ArtRender *render, ArtImageSource *image_source) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + + if (priv->image_source != NULL) + { + art_warn ("art_render_add_image_source: image source already present.\n"); + return; + } + priv->image_source = image_source; +} + +/* Solid image source object and methods. Perhaps this should go into a + separate file. */ + +typedef struct _ArtImageSourceSolid ArtImageSourceSolid; + +struct _ArtImageSourceSolid { + ArtImageSource super; + ArtPixMaxDepth color[ART_MAX_CHAN]; + art_u32 *rgbtab; + art_boolean init; +}; + +static void +art_render_image_solid_done (ArtRenderCallback *self, ArtRender *render) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + + if (z->rgbtab != NULL) + art_free (z->rgbtab); + art_free (self); +} + +static void +art_render_image_solid_rgb8_opaq_init (ArtImageSourceSolid *self, ArtRender *render) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtPixMaxDepth color_max; + int r_fg, g_fg, b_fg; + int r_bg, g_bg, b_bg; + int r, g, b; + int dr, dg, db; + int i; + int tmp; + art_u32 *rgbtab; + + rgbtab = art_new (art_u32, 256); + z->rgbtab = rgbtab; + + color_max = self->color[0]; + r_fg = ART_PIX_8_FROM_MAX (color_max); + color_max = self->color[1]; + g_fg = ART_PIX_8_FROM_MAX (color_max); + color_max = self->color[2]; + b_fg = ART_PIX_8_FROM_MAX (color_max); + + color_max = render->clear_color[0]; + r_bg = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[1]; + g_bg = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[2]; + b_bg = ART_PIX_8_FROM_MAX (color_max); + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + tmp = ((r_fg - r_bg) << 16) + 0x80; + dr = (tmp + (tmp >> 8)) >> 8; + tmp = ((g_fg - g_bg) << 16) + 0x80; + dg = (tmp + (tmp >> 8)) >> 8; + tmp = ((b_fg - b_bg) << 16) + 0x80; + db = (tmp + (tmp >> 8)) >> 8; + + for (i = 0; i < 256; i++) + { + rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16); + r += dr; + g += dg; + b += db; + } +} + +static void +art_render_image_solid_rgb8_opaq (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + art_u32 *rgbtab = z->rgbtab; + art_u32 rgb; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + int i; + int ix; + + if (n_run > 0) + { + run_x1 = run[0].x; + if (run_x1 > x0) + { + rgb = rgbtab[0]; + art_rgb_fill_run (dest, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - x0); + } + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run_x1; + run_x1 = run[i + 1].x; + rgb = rgbtab[(run[i].alpha >> 16) & 0xff]; + ix = (run_x0 - x0) * 3; +#define OPTIMIZE_LEN_1 +#ifdef OPTIMIZE_LEN_1 + if (run_x1 - run_x0 == 1) + { + dest[ix] = rgb >> 16; + dest[ix + 1] = (rgb >> 8) & 0xff; + dest[ix + 2] = rgb & 0xff; + } + else + { + art_rgb_fill_run (dest + ix, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); + } +#else + art_rgb_fill_run (dest + ix, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); +#endif + } + } + else + { + run_x1 = x0; + } + if (run_x1 < x1) + { + rgb = rgbtab[0]; + art_rgb_fill_run (dest + (run_x1 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - run_x1); + } +} + +static void +art_render_image_solid_rgb8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + int width = render->x1 - render->x0; + art_u8 r, g, b; + ArtPixMaxDepth color_max; + + /* todo: replace this simple test with real sparseness */ + if (z->init) + return; + z->init = ART_TRUE; + + color_max = z->color[0]; + r = ART_PIX_8_FROM_MAX (color_max); + color_max = z->color[1]; + g = ART_PIX_8_FROM_MAX (color_max); + color_max = z->color[2]; + b = ART_PIX_8_FROM_MAX (color_max); + + art_rgb_fill_run (render->image_buf, r, g, b, width); +} + +static void +art_render_image_solid_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtImageSourceFlags flags = 0; + static void (*render_cbk) (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y); + + render_cbk = NULL; + + if (render->depth == 8 && render->n_chan == 3 && + render->alpha_type == ART_ALPHA_NONE) + { + if (render->clear) + { + render_cbk = art_render_image_solid_rgb8_opaq; + flags |= ART_IMAGE_SOURCE_CAN_CLEAR | ART_IMAGE_SOURCE_CAN_COMPOSITE; + art_render_image_solid_rgb8_opaq_init (z, render); + } + } + if (render_cbk == NULL) + { + if (render->depth == 8) + { + render_cbk = art_render_image_solid_rgb8; + *p_buf_depth = 8; + *p_alpha = ART_ALPHA_NONE; /* todo */ + } + } + /* todo: general case */ + self->super.render = render_cbk; + *p_flags = flags; +} + +/** + * art_render_image_solid: Add a solid color image source. + * @render: The render object. + * @color: Color. + * + * Adds an image source with the solid color given by @color. The + * color need not be retained in memory after this call. + **/ +void +art_render_image_solid (ArtRender *render, ArtPixMaxDepth *color) +{ + ArtImageSourceSolid *image_source; + int i; + + image_source = art_new (ArtImageSourceSolid, 1); + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_image_solid_done; + image_source->super.negotiate = art_render_image_solid_negotiate; + + for (i = 0; i < render->n_chan; i++) + image_source->color[i] = color[i]; + + image_source->rgbtab = NULL; + image_source->init = ART_FALSE; + + art_render_add_image_source (render, &image_source->super); +} diff --git a/libart_lgpl/art_render.h b/libart_lgpl/art_render.h new file mode 100644 index 0000000000..399033cd0f --- /dev/null +++ b/libart_lgpl/art_render.h @@ -0,0 +1,179 @@ +/* + * art_render.h: Modular rendering architecture. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RENDER_H__ +#define __ART_RENDER_H__ + +#include <libart_lgpl/art_alphagamma.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Render object */ + +#ifndef ART_MAX_DEPTH +#define ART_MAX_DEPTH 16 +#endif + +#if ART_MAX_DEPTH == 16 +typedef art_u16 ArtPixMaxDepth; +#define ART_PIX_MAX_FROM_8(x) ((x) | ((x) << 8)) +#define ART_PIX_8_FROM_MAX(x) (((x) + 0x80 - (((x) + 0x80) >> 8)) >> 8) +#else +#if ART_MAX_DEPTH == 8 +typedef art_u8 ArtPixMaxDepth; +#define ART_PIX_MAX_FROM_8(x) (x) +#define ART_PIX_8_FROM_MAX(x) (x) +#else +#error ART_MAX_DEPTH must be either 8 or 16 +#endif +#endif + +#define ART_MAX_CHAN 16 + +typedef struct _ArtRender ArtRender; +typedef struct _ArtRenderCallback ArtRenderCallback; +typedef struct _ArtRenderMaskRun ArtRenderMaskRun; +typedef struct _ArtImageSource ArtImageSource; +typedef struct _ArtMaskSource ArtMaskSource; + +typedef enum { + ART_ALPHA_NONE = 0, + ART_ALPHA_SEPARATE = 1, + ART_ALPHA_PREMUL = 2 +} ArtAlphaType; + +typedef enum { + ART_COMPOSITE_NORMAL, + ART_COMPOSITE_MULTIPLY, + /* todo: more */ + ART_COMPOSITE_CUSTOM +} ArtCompositingMode; + +typedef enum { + ART_IMAGE_SOURCE_CAN_CLEAR = 1, + ART_IMAGE_SOURCE_CAN_COMPOSITE = 2 +} ArtImageSourceFlags; + +struct _ArtRenderMaskRun { + int x; + int alpha; +}; + +struct _ArtRenderCallback { + void (*render) (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y); + void (*done) (ArtRenderCallback *self, ArtRender *render); +}; + +struct _ArtImageSource { + ArtRenderCallback super; + void (*negotiate) (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha_type); +}; + +struct _ArtMaskSource { + ArtRenderCallback super; + int (*can_drive) (ArtMaskSource *self, ArtRender *render); + /* For each mask source, ::prepare() is invoked if it is not + a driver, or ::invoke_driver() if it is. */ + void (*invoke_driver) (ArtMaskSource *self, ArtRender *render); + void (*prepare) (ArtMaskSource *self, ArtRender *render, art_boolean first); +}; + +struct _ArtRender { + /* parameters of destination image */ + int x0, y0; + int x1, y1; + art_u8 *pixels; + int rowstride; + int n_chan; + int depth; + ArtAlphaType alpha_type; + + art_boolean clear; + ArtPixMaxDepth clear_color[ART_MAX_CHAN + 1]; + art_u32 opacity; /* [0..0x10000] */ + + ArtCompositingMode compositing_mode; + + ArtAlphaGamma *alphagamma; + + art_u8 *alpha_buf; + + /* parameters of intermediate buffer */ + int buf_depth; + ArtAlphaType buf_alpha; + art_u8 *image_buf; + + /* driving alpha scanline data */ + /* A "run" is a contiguous sequence of x values with the same alpha value. */ + int n_run; + ArtRenderMaskRun *run; + + /* A "span" is a contiguous sequence of x values with non-zero alpha. */ + int n_span; + int *span_x; + + art_boolean need_span; +}; + +ArtRender * +art_render_new (int x0, int y0, int x1, int y1, + art_u8 *pixels, int rowstride, + int n_chan, int depth, ArtAlphaType alpha_type, + ArtAlphaGamma *alphagamma); + +void +art_render_invoke (ArtRender *render); + +void +art_render_clear (ArtRender *render, const ArtPixMaxDepth *clear_color); + +void +art_render_clear_rgb (ArtRender *render, art_u32 clear_rgb); + +void +art_render_mask_solid (ArtRender *render, int opacity); + +void +art_render_image_solid (ArtRender *render, ArtPixMaxDepth *color); + +/* The next two functions are for custom mask sources only. */ +void +art_render_add_mask_source (ArtRender *render, ArtMaskSource *mask_source); + +void +art_render_invoke_callbacks (ArtRender *render, art_u8 *dest, int y); + +/* The following function is for custom image sources only. */ +void +art_render_add_image_source (ArtRender *render, ArtImageSource *image_source); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RENDER_H__ */ + diff --git a/libart_lgpl/art_render_gradient.c b/libart_lgpl/art_render_gradient.c new file mode 100644 index 0000000000..bf93c6fff1 --- /dev/null +++ b/libart_lgpl/art_render_gradient.c @@ -0,0 +1,716 @@ +/* + * art_render_gradient.c: Gradient image source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + * Alexander Larsson <alla@lysator.liu.se> + */ + +#include "config.h" +#include "art_render_gradient.h" + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +/* Hack to find out how to define alloca on different platforms. + * Modified version of glib/galloca.h. + */ + +#ifdef __GNUC__ +/* GCC does the right thing */ +# undef alloca +# define alloca(size) __builtin_alloca (size) +#elif defined (HAVE_ALLOCA_H) +/* a native and working alloca.h is there */ +# include <alloca.h> +#else /* !__GNUC__ && !HAVE_ALLOCA_H */ +# ifdef _MSC_VER +# include <malloc.h> +# define alloca _alloca +# else /* !_MSC_VER */ +# ifdef _AIX + #pragma alloca +# else /* !_AIX */ +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca (); +# endif /* !alloca */ +# endif /* !_AIX */ +# endif /* !_MSC_VER */ +#endif /* !__GNUC__ && !HAVE_ALLOCA_H */ + +#undef DEBUG_SPEW + +typedef struct _ArtImageSourceGradLin ArtImageSourceGradLin; +typedef struct _ArtImageSourceGradRad ArtImageSourceGradRad; + +/* The stops will be copied right after this structure */ +struct _ArtImageSourceGradLin { + ArtImageSource super; + ArtGradientLinear gradient; + ArtGradientStop stops[1]; +}; + +/* The stops will be copied right after this structure */ +struct _ArtImageSourceGradRad { + ArtImageSource super; + ArtGradientRadial gradient; + double a; + ArtGradientStop stops[1]; +}; + +#define EPSILON 1e-6 + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +static void +art_rgba_gradient_run (art_u8 *buf, + art_u8 *color1, + art_u8 *color2, + int len) +{ + int i; + int r, g, b, a; + int dr, dg, db, da; + +#ifdef DEBUG_SPEW + printf ("gradient run from %3d %3d %3d %3d to %3d %3d %3d %3d in %d pixels\n", + color1[0], color1[1], color1[2], color1[3], + color2[0], color2[1], color2[2], color2[3], + len); +#endif + + r = (color1[0] << 16) + 0x8000; + g = (color1[1] << 16) + 0x8000; + b = (color1[2] << 16) + 0x8000; + a = (color1[3] << 16) + 0x8000; + dr = ((color2[0] - color1[0]) << 16) / len; + dg = ((color2[1] - color1[1]) << 16) / len; + db = ((color2[2] - color1[2]) << 16) / len; + da = ((color2[3] - color1[3]) << 16) / len; + + for (i = 0; i < len; i++) + { + *buf++ = (r>>16); + *buf++ = (g>>16); + *buf++ = (b>>16); + *buf++ = (a>>16); + + r += dr; + g += dg; + b += db; + a += da; + } +} + +static void +calc_color_at (ArtGradientStop *stops, + int n_stops, + ArtGradientSpread spread, + double offset, + double offset_fraction, + int favor_start, + int ix, + art_u8 *color) +{ + double off0, off1; + int j; + + if (spread == ART_GRADIENT_PAD) + { + if (offset < 0.0) + { + color[0] = ART_PIX_8_FROM_MAX (stops[0].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[0].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[0].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[0].color[3]); + return; + } + if (offset >= 1.0) + { + color[0] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[3]); + return; + } + } + + if (ix > 0 && ix < n_stops) + { + off0 = stops[ix - 1].offset; + off1 = stops[ix].offset; + if (fabs (off1 - off0) > EPSILON) + { + double interp; + double o; + o = offset_fraction; + + if ((fabs (o) < EPSILON) && (!favor_start)) + o = 1.0; + else if ((fabs (o-1.0) < EPSILON) && (favor_start)) + o = 0.0; + + /* + if (offset_fraction == 0.0 && !favor_start) + offset_fraction = 1.0; + */ + + interp = (o - off0) / (off1 - off0); + for (j = 0; j < 4; j++) + { + int z0, z1; + int z; + z0 = stops[ix - 1].color[j]; + z1 = stops[ix].color[j]; + z = floor (z0 + (z1 - z0) * interp + 0.5); + color[j] = ART_PIX_8_FROM_MAX (z); + } + return; + } + /* If offsets are too close to safely do the division, just + pick the ix color. */ + color[0] = ART_PIX_8_FROM_MAX (stops[ix].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[ix].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[ix].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[ix].color[3]); + return; + } + + printf ("WARNING! bad ix %d in calc_color_at() [internal error]\n", ix); + assert (0); +} + +static void +art_render_gradient_linear_render_8 (ArtRenderCallback *self, + ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self; + const ArtGradientLinear *gradient = &(z->gradient); + int i; + int width = render->x1 - render->x0; + int len; + double offset, d_offset; + double offset_fraction; + int next_stop; + int ix; + art_u8 color1[4], color2[4]; + int n_stops = gradient->n_stops; + int extra_stops; + ArtGradientStop *stops = gradient->stops; + ArtGradientStop *tmp_stops; + art_u8 *bufp = render->image_buf; + ArtGradientSpread spread = gradient->spread; + +#ifdef DEBUG_SPEW + printf ("x1: %d, x2: %d, y: %d\n", render->x0, render->x1, y); + printf ("spread: %d, stops:", gradient->spread); + for (i=0;i<n_stops;i++) + { + printf ("%f, ", gradient->stops[i].offset); + } + printf ("\n"); + printf ("a: %f, b: %f, c: %f\n", gradient->a, gradient->b, gradient->c); +#endif + + offset = render->x0 * gradient->a + y * gradient->b + gradient->c; + d_offset = gradient->a; + + /* We need to force the gradient to extend the whole 0..1 segment, + because the rest of the code doesn't handle partial gradients + correctly */ + if ((gradient->stops[0].offset > EPSILON /* == 0.0 */) || + (gradient->stops[n_stops-1].offset < (1.0 - EPSILON))) + { + extra_stops = 0; + tmp_stops = stops = alloca (sizeof (ArtGradientStop) * (n_stops + 2)); + if (gradient->stops[0].offset > EPSILON /* 0.0 */) + { + memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop)); + tmp_stops[0].offset = 0.0; + tmp_stops += 1; + extra_stops++; + } + memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop) * n_stops); + if (gradient->stops[n_stops-1].offset < (1.0 - EPSILON)) + { + tmp_stops += n_stops; + memcpy (tmp_stops, &gradient->stops[n_stops-1], sizeof (ArtGradientStop)); + tmp_stops[0].offset = 1.0; + extra_stops++; + } + n_stops += extra_stops; + + +#ifdef DEBUG_SPEW + printf ("start/stop modified stops:"); + for (i=0;i<n_stops;i++) + { + printf ("%f, ", stops[i].offset); + } + printf ("\n"); +#endif + + } + + if (spread == ART_GRADIENT_REFLECT) + { + tmp_stops = stops; + stops = alloca (sizeof (ArtGradientStop) * n_stops * 2); + memcpy (stops, tmp_stops, sizeof (ArtGradientStop) * n_stops); + + for (i = 0; i< n_stops; i++) + { + stops[n_stops * 2 - 1 - i].offset = (1.0 - stops[i].offset / 2.0); + memcpy (stops[n_stops * 2 - 1 - i].color, stops[i].color, sizeof (stops[i].color)); + stops[i].offset = stops[i].offset / 2.0; + } + + spread = ART_GRADIENT_REPEAT; + offset = offset / 2.0; + d_offset = d_offset / 2.0; + + n_stops = 2 * n_stops; + +#ifdef DEBUG_SPEW + printf ("reflect modified stops:"); + for (i=0;i<n_stops;i++) + { + printf ("%f, ", stops[i].offset); + } + printf ("\n"); +#endif + } + + offset_fraction = offset - floor (offset); +#ifdef DEBUG_SPEW + printf ("inital offset: %f, fraction: %f d_offset: %f\n", offset, offset_fraction, d_offset); +#endif + /* ix is selected so that offset_fraction is + stops[ix-1] <= offset_fraction <= stops[ix] + If offset_fraction is equal to one of the edges, ix + is selected so the the section of the line extending + in the same direction as d_offset is between ix-1 and ix. + */ + for (ix = 0; ix < n_stops; ix++) + if (stops[ix].offset > offset_fraction || + (d_offset < 0.0 && fabs (stops[ix].offset - offset_fraction) < EPSILON)) + break; + if (ix == 0) + ix = n_stops - 1; + else if (ix == n_stops) + ix = n_stops - 1; + +#ifdef DEBUG_SPEW + printf ("Initial ix: %d\n", ix); +#endif + + assert (ix > 0); + assert (ix < n_stops); + assert ((stops[ix-1].offset <= offset_fraction + EPSILON) || + ((stops[ix].offset > (1.0 - EPSILON)) && (offset_fraction < EPSILON /* == 0.0*/))); + assert (offset_fraction <= stops[ix].offset); + /* FIXME: These asserts may be broken, it is for now + safer to not use them. Should be fixed! + See bug #121850 + assert ((offset_fraction != stops[ix-1].offset) || + (d_offset >= 0.0)); + assert ((offset_fraction != stops[ix].offset) || + (d_offset <= 0.0)); + */ + + while (width > 0) + { +#ifdef DEBUG_SPEW + printf ("ix: %d\n", ix); + printf ("start offset: %f\n", offset); +#endif + calc_color_at (stops, n_stops, + spread, + offset, + offset_fraction, + (d_offset > -EPSILON), + ix, + color1); + + if (d_offset > 0) + next_stop = ix; + else + next_stop = ix-1; + +#ifdef DEBUG_SPEW + printf ("next_stop: %d\n", next_stop); +#endif + if (fabs (d_offset) > EPSILON) + { + double o; + o = offset_fraction; + + if ((fabs (o) <= EPSILON) && (ix == n_stops - 1)) + o = 1.0; + else if ((fabs (o-1.0) <= EPSILON) && (ix == 1)) + o = 0.0; + +#ifdef DEBUG_SPEW + printf ("o: %f\n", o); +#endif + len = (int)floor (fabs ((stops[next_stop].offset - o) / d_offset)) + 1; + len = MAX (len, 0); + len = MIN (len, width); + } + else + { + len = width; + } +#ifdef DEBUG_SPEW + printf ("len: %d\n", len); +#endif + if (len > 0) + { + offset = offset + (len-1) * d_offset; + offset_fraction = offset - floor (offset); +#ifdef DEBUG_SPEW + printf ("end offset: %f, fraction: %f\n", offset, offset_fraction); +#endif + calc_color_at (stops, n_stops, + spread, + offset, + offset_fraction, + (d_offset < EPSILON), + ix, + color2); + + art_rgba_gradient_run (bufp, + color1, + color2, + len); + offset += d_offset; + offset_fraction = offset - floor (offset); + } + + if (d_offset > 0) + { + do + { + ix++; + if (ix == n_stops) + ix = 1; + /* Note: offset_fraction can actually be one here on x86 machines that + does calculations with extended precision, but later rounds to 64bit. + This happens if the 80bit offset_fraction is larger than the + largest 64bit double that is less than one. + */ + } + while (!((stops[ix-1].offset <= offset_fraction && + offset_fraction < stops[ix].offset) || + (ix == 1 && offset_fraction > (1.0 - EPSILON)))); + } + else + { + do + { + ix--; + if (ix == 0) + ix = n_stops - 1; + } + while (!((stops[ix-1].offset < offset_fraction && + offset_fraction <= stops[ix].offset) || + (ix == n_stops - 1 && offset_fraction < EPSILON /* == 0.0*/))); + } + + bufp += 4*len; + width -= len; + } +} + + +/** + * art_render_gradient_setpix: Set a gradient pixel. + * @render: The render object. + * @dst: Pointer to destination (where to store pixel). + * @n_stops: Number of stops in @stops. + * @stops: The stops for the gradient. + * @offset: The offset. + * + * @n_stops must be > 0. + * + * Sets a gradient pixel, storing it at @dst. + **/ +static void +art_render_gradient_setpix (ArtRender *render, + art_u8 *dst, + int n_stops, ArtGradientStop *stops, + double offset) +{ + int ix; + int j; + double off0, off1; + int n_ch = render->n_chan + 1; + + for (ix = 0; ix < n_stops; ix++) + if (stops[ix].offset > offset) + break; + /* stops[ix - 1].offset < offset < stops[ix].offset */ + if (ix > 0 && ix < n_stops) + { + off0 = stops[ix - 1].offset; + off1 = stops[ix].offset; + if (fabs (off1 - off0) > EPSILON) + { + double interp; + + interp = (offset - off0) / (off1 - off0); + for (j = 0; j < n_ch; j++) + { + int z0, z1; + int z; + z0 = stops[ix - 1].color[j]; + z1 = stops[ix].color[j]; + z = floor (z0 + (z1 - z0) * interp + 0.5); + if (render->buf_depth == 8) + dst[j] = ART_PIX_8_FROM_MAX (z); + else /* (render->buf_depth == 16) */ + ((art_u16 *)dst)[j] = z; + } + return; + } + } + else if (ix == n_stops) + ix--; + + for (j = 0; j < n_ch; j++) + { + int z; + z = stops[ix].color[j]; + if (render->buf_depth == 8) + dst[j] = ART_PIX_8_FROM_MAX (z); + else /* (render->buf_depth == 16) */ + ((art_u16 *)dst)[j] = z; + } +} + +static void +art_render_gradient_linear_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static void +art_render_gradient_linear_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self; + const ArtGradientLinear *gradient = &(z->gradient); + int pixstride = (render->n_chan + 1) * (render->depth >> 3); + int x; + int width = render->x1 - render->x0; + double offset, d_offset; + double actual_offset; + int n_stops = gradient->n_stops; + ArtGradientStop *stops = gradient->stops; + art_u8 *bufp = render->image_buf; + ArtGradientSpread spread = gradient->spread; + + offset = render->x0 * gradient->a + y * gradient->b + gradient->c; + d_offset = gradient->a; + + for (x = 0; x < width; x++) + { + if (spread == ART_GRADIENT_PAD) + actual_offset = offset; + else if (spread == ART_GRADIENT_REPEAT) + actual_offset = offset - floor (offset); + else /* (spread == ART_GRADIENT_REFLECT) */ + { + double tmp; + + tmp = offset - 2 * floor (0.5 * offset); + actual_offset = tmp > 1 ? 2 - tmp : tmp; + } + art_render_gradient_setpix (render, bufp, n_stops, stops, actual_offset); + offset += d_offset; + bufp += pixstride; + } +} + +static void +art_render_gradient_linear_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + if (render->depth == 8 && + render->n_chan == 3) + { + self->super.render = art_render_gradient_linear_render_8; + *p_flags = 0; + *p_buf_depth = 8; + *p_alpha = ART_ALPHA_PREMUL; + return; + } + + self->super.render = art_render_gradient_linear_render; + *p_flags = 0; + *p_buf_depth = render->depth; + *p_alpha = ART_ALPHA_PREMUL; +} + +/** + * art_render_gradient_linear: Add a linear gradient image source. + * @render: The render object. + * @gradient: The linear gradient. + * + * Adds the linear gradient @gradient as the image source for rendering + * in the render object @render. + **/ +void +art_render_gradient_linear (ArtRender *render, + const ArtGradientLinear *gradient, + ArtFilterLevel level) +{ + ArtImageSourceGradLin *image_source = art_alloc (sizeof (ArtImageSourceGradLin) + + sizeof (ArtGradientStop) * (gradient->n_stops - 1)); + + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_gradient_linear_done; + image_source->super.negotiate = art_render_gradient_linear_negotiate; + + /* copy the gradient into the structure */ + image_source->gradient = *gradient; + image_source->gradient.stops = image_source->stops; + memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops); + + art_render_add_image_source (render, &image_source->super); +} + +static void +art_render_gradient_radial_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static void +art_render_gradient_radial_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradRad *z = (ArtImageSourceGradRad *)self; + const ArtGradientRadial *gradient = &(z->gradient); + int pixstride = (render->n_chan + 1) * (render->depth >> 3); + int x; + int x0 = render->x0; + int width = render->x1 - x0; + int n_stops = gradient->n_stops; + ArtGradientStop *stops = gradient->stops; + art_u8 *bufp = render->image_buf; + double fx = gradient->fx; + double fy = gradient->fy; + double dx, dy; + const double *affine = gradient->affine; + double aff0 = affine[0]; + double aff1 = affine[1]; + const double a = z->a; + const double arecip = 1.0 / a; + double b, db; + double c, dc, ddc; + double b_a, db_a; + double rad, drad, ddrad; + + dx = x0 * aff0 + y * affine[2] + affine[4] - fx; + dy = x0 * aff1 + y * affine[3] + affine[5] - fy; + b = dx * fx + dy * fy; + db = aff0 * fx + aff1 * fy; + c = dx * dx + dy * dy; + dc = 2 * aff0 * dx + aff0 * aff0 + 2 * aff1 * dy + aff1 * aff1; + ddc = 2 * aff0 * aff0 + 2 * aff1 * aff1; + + b_a = b * arecip; + db_a = db * arecip; + + rad = b_a * b_a + c * arecip; + drad = 2 * b_a * db_a + db_a * db_a + dc * arecip; + ddrad = 2 * db_a * db_a + ddc * arecip; + + for (x = 0; x < width; x++) + { + double z; + + if (rad > 0) + z = b_a + sqrt (rad); + else + z = b_a; + art_render_gradient_setpix (render, bufp, n_stops, stops, z); + bufp += pixstride; + b_a += db_a; + rad += drad; + drad += ddrad; + } +} + +static void +art_render_gradient_radial_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + self->super.render = art_render_gradient_radial_render; + *p_flags = 0; + *p_buf_depth = render->depth; + *p_alpha = ART_ALPHA_PREMUL; +} + +/** + * art_render_gradient_radial: Add a radial gradient image source. + * @render: The render object. + * @gradient: The radial gradient. + * + * Adds the radial gradient @gradient as the image source for rendering + * in the render object @render. + **/ +void +art_render_gradient_radial (ArtRender *render, + const ArtGradientRadial *gradient, + ArtFilterLevel level) +{ + ArtImageSourceGradRad *image_source = art_alloc (sizeof (ArtImageSourceGradRad) + + sizeof (ArtGradientStop) * (gradient->n_stops - 1)); + double fx = gradient->fx; + double fy = gradient->fy; + + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_gradient_radial_done; + image_source->super.negotiate = art_render_gradient_radial_negotiate; + + /* copy the gradient into the structure */ + image_source->gradient = *gradient; + image_source->gradient.stops = image_source->stops; + memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops); + + /* todo: sanitycheck fx, fy? */ + image_source->a = 1 - fx * fx - fy * fy; + + art_render_add_image_source (render, &image_source->super); +} diff --git a/libart_lgpl/art_render_gradient.h b/libart_lgpl/art_render_gradient.h new file mode 100644 index 0000000000..ff539033f1 --- /dev/null +++ b/libart_lgpl/art_render_gradient.h @@ -0,0 +1,81 @@ +/* + * art_render_gradient.h: Gradient image source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + * Alexander Larsson <alla@lysator.liu.se> + */ + +#ifndef __ART_RENDER_GRADIENT_H__ +#define __ART_RENDER_GRADIENT_H__ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_render.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtGradientLinear ArtGradientLinear; +typedef struct _ArtGradientRadial ArtGradientRadial; +typedef struct _ArtGradientStop ArtGradientStop; + +typedef enum { + ART_GRADIENT_PAD, + ART_GRADIENT_REFLECT, + ART_GRADIENT_REPEAT +} ArtGradientSpread; + +struct _ArtGradientLinear { + double a; + double b; + double c; + ArtGradientSpread spread; + int n_stops; + ArtGradientStop *stops; +}; + +struct _ArtGradientRadial { + double affine[6]; /* transforms user coordinates to unit circle */ + double fx, fy; /* focal point in unit circle coords */ + int n_stops; + ArtGradientStop *stops; +}; + +struct _ArtGradientStop { + double offset; + ArtPixMaxDepth color[ART_MAX_CHAN + 1]; +}; + +void +art_render_gradient_linear (ArtRender *render, + const ArtGradientLinear *gradient, + ArtFilterLevel level); + +void +art_render_gradient_radial (ArtRender *render, + const ArtGradientRadial *gradient, + ArtFilterLevel level); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RENDER_GRADIENT_H__ */ diff --git a/libart_lgpl/art_render_mask.c b/libart_lgpl/art_render_mask.c new file mode 100644 index 0000000000..ce82608dcd --- /dev/null +++ b/libart_lgpl/art_render_mask.c @@ -0,0 +1,168 @@ +/* + * art_render_mask.c: Alpha mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + */ + +#include "config.h" +#include "art_render_mask.h" + +#include <string.h> + + +typedef struct _ArtMaskSourceMask ArtMaskSourceMask; + +struct _ArtMaskSourceMask { + ArtMaskSource super; + ArtRender *render; + art_boolean first; + int x0; + int y0; + int x1; + int y1; + const art_u8 *mask_buf; + int rowstride; +}; + +static void +art_render_mask_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static int +art_render_mask_can_drive (ArtMaskSource *self, ArtRender *render) +{ + return 0; +} + +static void +art_render_mask_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtMaskSourceMask *z = (ArtMaskSourceMask *)self; + int x0 = render->x0, x1 = render->x1; + int z_x0 = z->x0, z_x1 = z->x1; + int width = x1 - x0; + int z_width = z_x1 - z_x0; + art_u8 *alpha_buf = render->alpha_buf; + + if (y < z->y0 || y >= z->y1 || z_width <= 0) + memset (alpha_buf, 0, width); + else + { + const art_u8 *src_line = z->mask_buf + (y - z->y0) * z->rowstride; + art_u8 *dst_line = alpha_buf + z_x0 - x0; + + if (z_x0 > x0) + memset (alpha_buf, 0, z_x0 - x0); + + if (z->first) + memcpy (dst_line, src_line, z_width); + else + { + int x; + + for (x = 0; x < z_width; x++) + { + int v; + v = src_line[x]; + if (v) + { + v = v * dst_line[x] + 0x80; + v = (v + (v >> 8)) >> 8; + dst_line[x] = v; + } + else + { + dst_line[x] = 0; + } + } + } + + if (z_x1 < x1) + memset (alpha_buf + z_x1 - x0, 0, x1 - z_x1); + } +} + +static void +art_render_mask_prepare (ArtMaskSource *self, ArtRender *render, + art_boolean first) +{ + ArtMaskSourceMask *z = (ArtMaskSourceMask *)self; + self->super.render = art_render_mask_render; + z->first = first; +} + +/** + * art_render_mask: Use an alpha buffer as a render mask source. + * @render: Render object. + * @x0: Left coordinate of mask rect. + * @y0: Top coordinate of mask rect. + * @x1: Right coordinate of mask rect. + * @y1: Bottom coordinate of mask rect. + * @mask_buf: Buffer containing 8bpp alpha mask data. + * @rowstride: Rowstride of @mask_buf. + * + * Adds @mask_buf to the render object as a mask. Note: @mask_buf must + * remain allocated until art_render_invoke() is called on @render. + **/ +void +art_render_mask (ArtRender *render, + int x0, int y0, int x1, int y1, + const art_u8 *mask_buf, int rowstride) +{ + ArtMaskSourceMask *mask_source; + + if (x0 < render->x0) + { + mask_buf += render->x0 - x0; + x0 = render->x0; + } + if (x1 > render->x1) + x1 = render->x1; + + if (y0 < render->y0) + { + mask_buf += (render->y0 - y0) * rowstride; + y0 = render->y0; + } + if (y1 > render->y1) + y1 = render->y1; + + mask_source = art_new (ArtMaskSourceMask, 1); + + mask_source->super.super.render = NULL; + mask_source->super.super.done = art_render_mask_done; + mask_source->super.can_drive = art_render_mask_can_drive; + mask_source->super.invoke_driver = NULL; + mask_source->super.prepare = art_render_mask_prepare; + mask_source->render = render; + mask_source->x0 = x0; + mask_source->y0 = y0; + mask_source->x1 = x1; + mask_source->y1 = y1; + mask_source->mask_buf = mask_buf; + mask_source->rowstride = rowstride; + + art_render_add_mask_source (render, &mask_source->super); + +} diff --git a/libart_lgpl/art_render_mask.h b/libart_lgpl/art_render_mask.h new file mode 100644 index 0000000000..ef126e8042 --- /dev/null +++ b/libart_lgpl/art_render_mask.h @@ -0,0 +1,43 @@ +/* + * art_render_mask.h: Alpha mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + */ + +#ifndef __ART_RENDER_MASK_H__ +#define __ART_RENDER_MASK_H__ + +#include <libart_lgpl/art_render.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_render_mask (ArtRender *render, + int x0, int y0, int x1, int y1, + const art_u8 *mask_buf, int rowstride); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RENDER_MASK_H__ */ diff --git a/libart_lgpl/art_render_svp.c b/libart_lgpl/art_render_svp.c new file mode 100644 index 0000000000..af75b7718e --- /dev/null +++ b/libart_lgpl/art_render_svp.c @@ -0,0 +1,421 @@ +/* + * art_render_gradient.c: SVP mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + */ + +#include "art_render_svp.h" +#include "art_svp_render_aa.h" + +typedef struct _ArtMaskSourceSVP ArtMaskSourceSVP; + +struct _ArtMaskSourceSVP { + ArtMaskSource super; + ArtRender *render; + const ArtSVP *svp; + art_u8 *dest_ptr; +}; + +static void +art_render_svp_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static int +art_render_svp_can_drive (ArtMaskSource *self, ArtRender *render) +{ + return 10; +} + +/* The basic art_render_svp_callback function is repeated four times, + for all combinations of non-unit opacity and generating spans. In + general, I'd consider this bad style, but in this case I plead + a measurable performance improvement. */ + +static void +art_render_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int i; + int running_sum = start; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0 && running_sum > 0x80ff) + { + run[0].x = x0; + run[0].alpha = running_sum; + n_run++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + run[n_run].alpha = running_sum; + n_run++; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + run[n_run].alpha = running_sum; + n_run++; + } + if (running_sum > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + } + + render->n_run = n_run; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_span (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int n_span = 0; + int i; + int running_sum = start; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + int *span_x = render->span_x; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0 && running_sum > 0x80ff) + { + run[0].x = x0; + run[0].alpha = running_sum; + n_run++; + span_x[0] = x0; + n_span++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + run[n_run].alpha = running_sum; + n_run++; + if ((n_span & 1) != (running_sum > 0x80ff)) + span_x[n_span++] = run_x0; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + run[n_run].alpha = running_sum; + n_run++; + if ((n_span & 1) != (running_sum > 0x80ff)) + span_x[n_span++] = run_x1; + } + if (running_sum > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + span_x[n_span++] = x1; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + span_x[0] = x0; + span_x[1] = x1; + n_span = 2; + } + + render->n_run = n_run; + render->n_span = n_span; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_opacity (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int i; + art_u32 running_sum; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + art_u32 opacity = render->opacity; + art_u32 alpha; + + running_sum = start - 0x7f80; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + if (run_x1 > x0 && alpha > 0x80ff) + { + run[0].x = x0; + run[0].alpha = alpha; + n_run++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + run[n_run].alpha = alpha; + n_run++; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + run[n_run].alpha = alpha; + n_run++; + } + if (alpha > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + } + + render->n_run = n_run; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_opacity_span (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int n_span = 0; + int i; + art_u32 running_sum; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + int *span_x = render->span_x; + art_u32 opacity = render->opacity; + art_u32 alpha; + + running_sum = start - 0x7f80; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + if (run_x1 > x0 && alpha > 0x80ff) + { + run[0].x = x0; + run[0].alpha = alpha; + n_run++; + span_x[0] = x0; + n_span++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + run[n_run].alpha = alpha; + n_run++; + if ((n_span & 1) != (alpha > 0x80ff)) + span_x[n_span++] = run_x0; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + run[n_run].alpha = alpha; + n_run++; + if ((n_span & 1) != (alpha > 0x80ff)) + span_x[n_span++] = run_x1; + } + if (alpha > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + span_x[n_span++] = x1; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + span_x[0] = x0; + span_x[1] = x1; + n_span = 2; + } + + render->n_run = n_run; + render->n_span = n_span; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_invoke_driver (ArtMaskSource *self, ArtRender *render) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)self; + void (*callback) (void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps); + + z->dest_ptr = render->pixels; + if (render->opacity == 0x10000) + { + if (render->need_span) + callback = art_render_svp_callback_span; + else + callback = art_render_svp_callback; + } + else + { + if (render->need_span) + callback = art_render_svp_callback_opacity_span; + else + callback = art_render_svp_callback_opacity; + } + + art_svp_render_aa (z->svp, + render->x0, render->y0, + render->x1, render->y1, callback, + self); + art_render_svp_done (&self->super, render); +} + +G_GNUC_NORETURN static void +art_render_svp_prepare (ArtMaskSource *self, ArtRender *render, + art_boolean first) +{ + /* todo */ + art_die ("art_render_svp non-driver mode not yet implemented.\n"); +} + +/** + * art_render_svp: Use an SVP as a render mask source. + * @render: Render object. + * @svp: SVP. + * + * Adds @svp to the render object as a mask. Note: @svp must remain + * allocated until art_render_invoke() is called on @render. + **/ +void +art_render_svp (ArtRender *render, const ArtSVP *svp) +{ + ArtMaskSourceSVP *mask_source; + mask_source = art_new (ArtMaskSourceSVP, 1); + + mask_source->super.super.render = NULL; + mask_source->super.super.done = art_render_svp_done; + mask_source->super.can_drive = art_render_svp_can_drive; + mask_source->super.invoke_driver = art_render_svp_invoke_driver; + mask_source->super.prepare = art_render_svp_prepare; + mask_source->render = render; + mask_source->svp = svp; + + art_render_add_mask_source (render, &mask_source->super); +} diff --git a/libart_lgpl/art_render_svp.h b/libart_lgpl/art_render_svp.h new file mode 100644 index 0000000000..edb1f92374 --- /dev/null +++ b/libart_lgpl/art_render_svp.h @@ -0,0 +1,42 @@ +/* + * art_render_svp.h: SVP mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien <raph@acm.org> + */ + +#ifndef __ART_RENDER_SVP_H__ +#define __ART_RENDER_SVP_H__ + +#include <libart_lgpl/art_render.h> +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_render_svp (ArtRender *render, const ArtSVP *svp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RENDER_SVP_H__ */ diff --git a/libart_lgpl/art_rgb.c b/libart_lgpl/art_rgb.c new file mode 100644 index 0000000000..05bfa02289 --- /dev/null +++ b/libart_lgpl/art_rgb.c @@ -0,0 +1,175 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb.h" + +#include <string.h> /* for memset */ + +/* Basic operators for manipulating 24-bit packed RGB buffers. */ + +#define COLOR_RUN_COMPLEX + +#ifdef COLOR_RUN_SIMPLE +/* This is really slow. Is there any way we might speed it up? + Two ideas: + + First, maybe we should be working at 32-bit alignment. Then, + this can be a simple loop over word stores. + + Second, we can keep working at 24-bit alignment, but have some + intelligence about storing. For example, we can iterate over + 4-pixel chunks (aligned at 4 pixels), with an inner loop + something like: + + *buf++ = v1; + *buf++ = v2; + *buf++ = v3; + + One source of extra complexity is the need to make sure linebuf is + aligned to a 32-bit boundary. + + This second alternative has some complexity to it, but is + appealing because it really minimizes the memory bandwidth. */ +void +art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, gint n) +{ + int i; + + if (r == g && g == b) + { + memset (buf, g, n + n + n); + } + else + { + for (i = 0; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } +} +#endif + +#ifdef COLOR_RUN_COMPLEX +/* This implements the second of the two ideas above. The test results + are _very_ encouraging - it seems the speed is within 10% of + memset, which is quite good! */ +/** + * art_rgb_fill_run: fill a buffer a solid RGB color. + * @buf: Buffer to fill. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @n: Number of RGB triples to fill. + * + * Fills a buffer with @n copies of the (@r, @g, @b) triple. Thus, + * locations @buf (inclusive) through @buf + 3 * @n (exclusive) are + * written. + * + * The implementation of this routine is very highly optimized. + **/ +void +art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) +{ + int i; + unsigned int v1, v2, v3; + + if (r == g && g == b) + { + memset (buf, g, n + n + n); + } + else + { + if (n < 8) + { + for (i = 0; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } else { + /* handle prefix up to byte alignment */ + /* I'm worried about this cast on sizeof(long) != sizeof(uchar *) + architectures, but it _should_ work. */ + for (i = 0; ((unsigned long)buf) & 3; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } +#ifndef WORDS_BIGENDIAN + v1 = r | (g << 8) | (b << 16) | (r << 24); + v3 = (v1 << 8) | b; + v2 = (v3 << 8) | g; +#else + v1 = (r << 24) | (g << 16) | (b << 8) | r; + v2 = (v1 << 8) | g; + v3 = (v2 << 8) | b; +#endif + for (; i < n - 3; i += 4) + { + ((art_u32 *)buf)[0] = v1; + ((art_u32 *)buf)[1] = v2; + ((art_u32 *)buf)[2] = v3; + buf += 12; + } + /* handle postfix */ + for (; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } + } +} +#endif + +/** + * art_rgb_run_alpha: Render semitransparent color over RGB buffer. + * @buf: Buffer for rendering. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @alpha: Alpha, range 0..256. + * @n: Number of RGB triples to render. + * + * Renders a sequential run of solid (@r, @g, @b) color over @buf with + * opacity @alpha. + **/ +void +art_rgb_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) +{ + int i; + int v; + + for (i = 0; i < n; i++) + { + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + } +} + diff --git a/libart_lgpl/art_rgb.h b/libart_lgpl/art_rgb.h new file mode 100644 index 0000000000..c195a5c43f --- /dev/null +++ b/libart_lgpl/art_rgb.h @@ -0,0 +1,40 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_H__ +#define __ART_RGB_H__ + +#include <libart_lgpl/art_misc.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n); + +void +art_rgb_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, + int n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_a_affine.c b/libart_lgpl/art_rgb_a_affine.c new file mode 100644 index 0000000000..ff38e2a5eb --- /dev/null +++ b/libart_lgpl/art_rgb_a_affine.c @@ -0,0 +1,149 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_a_affine.h" + +#include <math.h> + +#include "art_affine.h" +#include "art_point.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed alpha only images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ + +/** + * art_rgb_a_affine: Affine transform source Alpha image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image alpha buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @rgb: RGB foreground color, in 0xRRGGBB. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the solid color rgb with alpha specified by the + * source image stored in @src, compositing over the area of destination + * image @dst specified by the rectangle (@x0, @y0) - (@x1, @y1). + * As usual in libart, the left and top edges of this rectangle are + * included, and the right and bottom edges are excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_a_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgb, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + int tmp; + int run_x0, run_x1; + art_u8 r, g, b; + + r = (rgb>>16)&0xff; + g = (rgb>>8)&0xff; + b = (rgb)&0xff; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x; + if (src_x >= 0 && src_x < src_width && + src_y >= 0 && src_y < src_height) + { + + alpha = *src_p; + if (alpha) + { + if (alpha == 255) + { + dst_p[0] = r; + dst_p[1] = g; + dst_p[2] = b; + } + else + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + tmp = (r - bg_r) * alpha; + fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (g - bg_g) * alpha; + fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (b - bg_b) * alpha; + fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + } + } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/libart_lgpl/art_rgb_a_affine.h b/libart_lgpl/art_rgb_a_affine.h new file mode 100644 index 0000000000..756fcbaaee --- /dev/null +++ b/libart_lgpl/art_rgb_a_affine.h @@ -0,0 +1,47 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_A_AFFINE_H__ +#define __ART_RGB_A_AFFINE_H__ + +/* This module handles compositing of affine-transformed alpha only images + over rgb pixel buffers. */ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_alphagamma.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_a_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgb, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_affine.c b/libart_lgpl/art_rgb_affine.c new file mode 100644 index 0000000000..1d826672d1 --- /dev/null +++ b/libart_lgpl/art_rgb_affine.c @@ -0,0 +1,106 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_affine.h" + +#include <math.h> +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed rgb images + over rgb pixel buffers. */ + +/** + * art_rgb_affine: Affine transform source RGB image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image RGB buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be done + * in a gamma-corrected color space. Since the source image is opaque RGB, + * this argument only affects the edges. In the current implementation, + * it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_affine (art_u8 *dst, int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int run_x0, run_x1; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x * 3; + dst_p[0] = src_p[0]; + dst_p[1] = src_p[1]; + dst_p[2] = src_p[2]; + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/libart_lgpl/art_rgb_affine.h b/libart_lgpl/art_rgb_affine.h new file mode 100644 index 0000000000..1bf146ae70 --- /dev/null +++ b/libart_lgpl/art_rgb_affine.h @@ -0,0 +1,45 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_AFFINE_H__ +#define __ART_RGB_AFFINE_H__ + +/* This module handles compositing of affine-transformed rgb images + over rgb pixel buffers. */ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_alphagamma.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_affine (art_u8 *dst, int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_affine_private.c b/libart_lgpl/art_rgb_affine_private.c new file mode 100644 index 0000000000..679e114c14 --- /dev/null +++ b/libart_lgpl/art_rgb_affine_private.c @@ -0,0 +1,127 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_affine_private.h" + +#include <math.h> +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" + +/* Private functions for the rgb affine image compositors - primarily, + the determination of runs, eliminating the need for source image + bbox calculation in the inner loop. */ + +/* Determine a "run", such that the inverse affine of all pixels from + (x0, y) inclusive to (x1, y) exclusive fit within the bounds + of the source image. + + Initial values of x0, x1, and result values stored in first two + pointer arguments. +*/ + +#define EPSILON 1e-6 + +void +art_rgb_affine_run (int *p_x0, int *p_x1, int y, + int src_width, int src_height, + const double affine[6]) +{ + int x0, x1; + double z; + double x_intercept; + int xi; + + x0 = *p_x0; + x1 = *p_x1; + + /* do left and right edges */ + if (affine[0] > EPSILON) + { + z = affine[2] * (y + 0.5) + affine[4]; + x_intercept = -z / affine[0]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = (-z + src_width) / affine[0]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else if (affine[0] < -EPSILON) + { + z = affine[2] * (y + 0.5) + affine[4]; + x_intercept = (-z + src_width) / affine[0]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = -z / affine[0]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else + { + z = affine[2] * (y + 0.5) + affine[4]; + if (z < 0 || z >= src_width) + { + *p_x1 = *p_x0; + return; + } + } + + /* do top and bottom edges */ + if (affine[1] > EPSILON) + { + z = affine[3] * (y + 0.5) + affine[5]; + x_intercept = -z / affine[1]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = (-z + src_height) / affine[1]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else if (affine[1] < -EPSILON) + { + z = affine[3] * (y + 0.5) + affine[5]; + x_intercept = (-z + src_height) / affine[1]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = -z / affine[1]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else + { + z = affine[3] * (y + 0.5) + affine[5]; + if (z < 0 || z >= src_height) + { + *p_x1 = *p_x0; + return; + } + } + + *p_x0 = x0; + *p_x1 = x1; +} diff --git a/libart_lgpl/art_rgb_affine_private.h b/libart_lgpl/art_rgb_affine_private.h new file mode 100644 index 0000000000..edaf0e3aaa --- /dev/null +++ b/libart_lgpl/art_rgb_affine_private.h @@ -0,0 +1,39 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_AFFINE_PRIVATE_H__ +#define __ART_RGB_AFFINE_PRIVATE_H__ + +/* This module handles compositing of affine-transformed rgb images + over rgb pixel buffers. */ + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_affine_run (int *p_x0, int *p_x1, int y, + int src_width, int src_height, + const double affine[6]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_bitmap_affine.c b/libart_lgpl/art_rgb_bitmap_affine.c new file mode 100644 index 0000000000..825f8b5638 --- /dev/null +++ b/libart_lgpl/art_rgb_bitmap_affine.c @@ -0,0 +1,198 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_bitmap_affine.h" + +#include <math.h> +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed bitmap images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. Foreground color is given and assumed to be + opaque, background color is assumed to be fully transparent. */ + +static void +art_rgb_bitmap_affine_opaque (art_u8 *dst, + int x0, int y0, int x1, int y1, + int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgb, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + art_u8 r, g, b; + int run_x0, run_x1; + + r = rgb >> 16; + g = (rgb >> 8) & 0xff; + b = rgb & 0xff; + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + (src_x >> 3); + if (*src_p & (128 >> (src_x & 7))) + { + dst_p[0] = r; + dst_p[1] = g; + dst_p[2] = b; + } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} +/* Composite the source image over the destination image, applying the + affine transform. Foreground color is given, background color is + assumed to be fully transparent. */ + +/** + * art_rgb_bitmap_affine: Affine transform source bitmap image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image bitmap buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @rgba: RGBA foreground color, in 0xRRGGBBAA. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). + * + * The source bitmap stored with MSB as the leftmost pixel. Source 1 + * bits correspond to the semitransparent color @rgba, while source 0 + * bits are transparent. + * + * See art_rgb_affine() for a description of additional parameters. + **/ +void +art_rgb_bitmap_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgba, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + art_u8 r, g, b; + int run_x0, run_x1; + + alpha = rgba & 0xff; + if (alpha == 0xff) + { + art_rgb_bitmap_affine_opaque (dst, x0, y0, x1, y1, dst_rowstride, + src, + src_width, src_height, src_rowstride, + rgba >> 8, + affine, + level, + alphagamma); + return; + } + /* alpha = (65536 * alpha) / 255; */ + alpha = (alpha << 8) + alpha + (alpha >> 7); + r = rgba >> 24; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + (src_x >> 3); + if (*src_p & (128 >> (src_x & 7))) + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + fg_r = bg_r + (((r - bg_r) * alpha + 0x8000) >> 16); + fg_g = bg_g + (((g - bg_g) * alpha + 0x8000) >> 16); + fg_b = bg_b + (((b - bg_b) * alpha + 0x8000) >> 16); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/libart_lgpl/art_rgb_bitmap_affine.h b/libart_lgpl/art_rgb_bitmap_affine.h new file mode 100644 index 0000000000..6c7d21d648 --- /dev/null +++ b/libart_lgpl/art_rgb_bitmap_affine.h @@ -0,0 +1,47 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_BITMAP_AFFINE_H__ +#define __ART_RGB_BITMAP_AFFINE_H__ + +/* This module handles compositing of affine-transformed bitmap images + over rgb pixel buffers. */ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_alphagamma.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_bitmap_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgba, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_pixbuf_affine.c b/libart_lgpl/art_rgb_pixbuf_affine.c new file mode 100644 index 0000000000..0a25b575db --- /dev/null +++ b/libart_lgpl/art_rgb_pixbuf_affine.c @@ -0,0 +1,104 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_pixbuf_affine.h" + +#include <math.h> +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_pixbuf.h" +#include "art_rgb_affine.h" +#include "art_rgb_affine.h" +#include "art_rgb_rgba_affine.h" + +/* This module handles compositing of affine-transformed generic + pixbuf images over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ +/** + * art_rgb_pixbuf_affine: Affine transform source RGB pixbuf and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @pixbuf: source image pixbuf. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_pixbuf_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const ArtPixBuf *pixbuf, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + if (pixbuf->format != ART_PIX_RGB) + { + art_warn ("art_rgb_pixbuf_affine: need RGB format image\n"); + return; + } + + if (pixbuf->bits_per_sample != 8) + { + art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n"); + return; + } + + if (pixbuf->n_channels != 3 + (pixbuf->has_alpha != 0)) + { + art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n"); + return; + } + + if (pixbuf->has_alpha) + art_rgb_rgba_affine (dst, x0, y0, x1, y1, dst_rowstride, + pixbuf->pixels, + pixbuf->width, pixbuf->height, pixbuf->rowstride, + affine, + level, + alphagamma); + else + art_rgb_affine (dst, x0, y0, x1, y1, dst_rowstride, + pixbuf->pixels, + pixbuf->width, pixbuf->height, pixbuf->rowstride, + affine, + level, + alphagamma); +} diff --git a/libart_lgpl/art_rgb_pixbuf_affine.h b/libart_lgpl/art_rgb_pixbuf_affine.h new file mode 100644 index 0000000000..8d5b466858 --- /dev/null +++ b/libart_lgpl/art_rgb_pixbuf_affine.h @@ -0,0 +1,46 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_PIXBUF_AFFINE_H__ +#define __ART_RGB_PIXBUF_AFFINE_H__ + +/* This module handles compositing of affine-transformed generic + pixbuf images over rgb pixel buffers. */ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_alphagamma.h> +#include <libart_lgpl/art_pixbuf.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_pixbuf_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const ArtPixBuf *pixbuf, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_rgba_affine.c b/libart_lgpl/art_rgb_rgba_affine.c new file mode 100644 index 0000000000..41c7397270 --- /dev/null +++ b/libart_lgpl/art_rgb_rgba_affine.c @@ -0,0 +1,142 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_rgba_affine.h" + +#include <math.h> +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed rgba images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ + +/** + * art_rgb_rgba_affine: Affine transform source RGBA image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image RGBA buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_rgba_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + int tmp; + int run_x0, run_x1; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x * 4; + if (src_x >= 0 && src_x < src_width && + src_y >= 0 && src_y < src_height) + { + + alpha = src_p[3]; + if (alpha) + { + if (alpha == 255) + { + dst_p[0] = src_p[0]; + dst_p[1] = src_p[1]; + dst_p[2] = src_p[2]; + } + else + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + tmp = (src_p[0] - bg_r) * alpha; + fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (src_p[1] - bg_g) * alpha; + fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (src_p[2] - bg_b) * alpha; + fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + } + } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/libart_lgpl/art_rgb_rgba_affine.h b/libart_lgpl/art_rgb_rgba_affine.h new file mode 100644 index 0000000000..9428231798 --- /dev/null +++ b/libart_lgpl/art_rgb_rgba_affine.h @@ -0,0 +1,46 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_RGBA_AFFINE_H__ +#define __ART_RGB_RGBA_AFFINE_H__ + +/* This module handles compositing of affine-transformed rgba images + over rgb pixel buffers. */ + +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_alphagamma.h> + +#ifdef __cplusplus +extern "C" { +#endif + +void +art_rgb_rgba_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libart_lgpl/art_rgb_svp.c b/libart_lgpl/art_rgb_svp.c new file mode 100644 index 0000000000..d03101653d --- /dev/null +++ b/libart_lgpl/art_rgb_svp.c @@ -0,0 +1,457 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Render a sorted vector path into an RGB buffer. */ + +#include "config.h" +#include "art_rgb_svp.h" + +#include "art_svp.h" +#include "art_svp_render_aa.h" +#include "art_rgb.h" + +typedef struct _ArtRgbSVPData ArtRgbSVPData; +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPData { + art_u32 rgbtab[256]; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_rgb_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPData *data = (ArtRgbSVPData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + art_u32 rgb; + int x0, x1; + int k; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf + (run_x0 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf + (run_x1 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - run_x1); + } + } + else + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - x0); + } + + data->buf += data->rowstride; +} + +/* Render the vector path into the RGB buffer. */ + +/** + * art_rgb_svp_aa: Render sorted vector path into RGB buffer. + * @svp: The source sorted vector path. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @fg_color: Foreground color in 0xRRGGBB format. + * @bg_color: Background color in 0xRRGGBB format. + * @buf: Destination RGB buffer. + * @rowstride: Rowstride of @buf buffer. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the rendering. + * + * Renders the shape specified with @svp into the @buf RGB buffer. + * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height, + * of the rectangle rendered. The new pixels are stored starting at + * the first byte of @buf. Thus, the @x0 and @y0 parameters specify + * an offset within @svp, and may be tweaked as a way of doing + * integer-pixel translations without fiddling with @svp itself. + * + * The @fg_color and @bg_color arguments specify the opaque colors to + * be used for rendering. For pixels of entirely 0 winding-number, + * @bg_color is used. For pixels of entirely 1 winding number, + * @fg_color is used. In between, the color is interpolated based on + * the fraction of the pixel with a winding number of 1. If + * @alphagamma is NULL, then linear interpolation (in pixel counts) is + * the default. Otherwise, the interpolation is as specified by + * @alphagamma. + **/ +void +art_rgb_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 fg_color, art_u32 bg_color, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) +{ + ArtRgbSVPData data; + + int r_fg, g_fg, b_fg; + int r_bg, g_bg, b_bg; + int r, g, b; + int dr, dg, db; + int i; + + if (alphagamma == NULL) + { + r_fg = fg_color >> 16; + g_fg = (fg_color >> 8) & 0xff; + b_fg = fg_color & 0xff; + + r_bg = bg_color >> 16; + g_bg = (bg_color >> 8) & 0xff; + b_bg = bg_color & 0xff; + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + dr = ((r_fg - r_bg) << 16) / 255; + dg = ((g_fg - g_bg) << 16) / 255; + db = ((b_fg - b_bg) << 16) / 255; + + for (i = 0; i < 256; i++) + { + data.rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16); + r += dr; + g += dg; + b += db; + } + } + else + { + int *table; + art_u8 *invtab; + + table = alphagamma->table; + + r_fg = table[fg_color >> 16]; + g_fg = table[(fg_color >> 8) & 0xff]; + b_fg = table[fg_color & 0xff]; + + r_bg = table[bg_color >> 16]; + g_bg = table[(bg_color >> 8) & 0xff]; + b_bg = table[bg_color & 0xff]; + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + dr = ((r_fg - r_bg) << 16) / 255; + dg = ((g_fg - g_bg) << 16) / 255; + db = ((b_fg - b_bg) << 16) / 255; + + invtab = alphagamma->invtable; + for (i = 0; i < 256; i++) + { + data.rgbtab[i] = (invtab[r >> 16] << 16) | + (invtab[g >> 16] << 8) | + invtab[b >> 16]; + r += dr; + g += dg; + b += db; + } + } + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_callback, &data); +} + +static void +art_rgb_svp_alpha_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + else + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + + data->buf += data->rowstride; +} + +static void +art_rgb_svp_alpha_opaque_callback (void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf, + r, g, b, + run_x1 - x0); + else + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf + (run_x0 - x0) * 3, + r, g, b, + run_x1 - run_x0); + else + art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf + (run_x1 - x0) * 3, + r, g, b, + x1 - run_x1); + else + art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + } + else + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf, + r, g, b, + x1 - x0); + else + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + } + + data->buf += data->rowstride; +} + +/** + * art_rgb_svp_alpha: Alpha-composite sorted vector path over RGB buffer. + * @svp: The source sorted vector path. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @rgba: Color in 0xRRGGBBAA format. + * @buf: Destination RGB buffer. + * @rowstride: Rowstride of @buf buffer. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Renders the shape specified with @svp over the @buf RGB buffer. + * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height, + * of the rectangle rendered. The new pixels are stored starting at + * the first byte of @buf. Thus, the @x0 and @y0 parameters specify + * an offset within @svp, and may be tweaked as a way of doing + * integer-pixel translations without fiddling with @svp itself. + * + * The @rgba argument specifies the color for the rendering. Pixels of + * entirely 0 winding number are left untouched. Pixels of entirely + * 1 winding number have the color @rgba composited over them (ie, + * are replaced by the red, green, blue components of @rgba if the alpha + * component is 0xff). Pixels of intermediate coverage are interpolated + * according to the rule in @alphagamma, or default to linear if + * @alphagamma is NULL. + **/ +void +art_rgb_svp_alpha (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 rgba, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) +{ + ArtRgbSVPAlphaData data; + int r, g, b, alpha; + int i; + int a, da; + + r = rgba >> 24; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + alpha = rgba & 0xff; + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) + { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback, + &data); + else + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback, &data); +} + diff --git a/libart_lgpl/art_rgb_svp.h b/libart_lgpl/art_rgb_svp.h new file mode 100644 index 0000000000..c76aa40ef1 --- /dev/null +++ b/libart_lgpl/art_rgb_svp.h @@ -0,0 +1,50 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGB_SVP_H__ +#define __ART_RGB_SVP_H__ + +/* Render a sorted vector path into an RGB buffer. */ + +#include <libart_lgpl/art_alphagamma.h> +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_rgb_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 fg_color, art_u32 bg_color, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma); + +void +art_rgb_svp_alpha (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 rgba, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_RGB_SVP_H__ */ diff --git a/libart_lgpl/art_rgba.c b/libart_lgpl/art_rgba.c new file mode 100644 index 0000000000..cde183cc52 --- /dev/null +++ b/libart_lgpl/art_rgba.c @@ -0,0 +1,258 @@ +/* + * art_rgba.c: Functions for manipulating RGBA pixel data. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgba.h" + +#define ART_OPTIMIZE_SPACE + +#ifndef ART_OPTIMIZE_SPACE +#include "art_rgba_table.c" +#endif + +/** + * art_rgba_rgba_composite: Composite RGBA image over RGBA buffer. + * @dst: Destination RGBA buffer. + * @src: Source RGBA buffer. + * @n: Number of RGBA pixels to composite. + * + * Composites the RGBA pixels in @dst over the @src buffer. + **/ +void +art_rgba_rgba_composite (art_u8 *dst, const art_u8 *src, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba, dst_rgba; +#else + art_u32 src_abgr, dst_abgr; +#endif + art_u8 src_alpha, dst_alpha; + + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + src_rgba = ((art_u32 *)src)[i]; + src_alpha = src_rgba & 0xff; +#else + src_abgr = ((art_u32 *)src)[i]; + src_alpha = (src_abgr >> 24) & 0xff; +#endif + if (src_alpha) + { + if (src_alpha == 0xff || + ( +#ifdef WORDS_BIGENDIAN + dst_rgba = ((art_u32 *)dst)[i], + dst_alpha = dst_rgba & 0xff, +#else + dst_abgr = ((art_u32 *)dst)[i], + dst_alpha = (dst_abgr >> 24), +#endif + dst_alpha == 0)) +#ifdef WORDS_BIGENDIAN + ((art_u32 *)dst)[i] = src_rgba; +#else + ((art_u32 *)dst)[i] = src_abgr; +#endif + else + { + int r, g, b, a; + int src_r, src_g, src_b; + int dst_r, dst_g, dst_b; + int tmp; + int c; + +#ifdef ART_OPTIMIZE_SPACE + tmp = (255 - src_alpha) * (255 - dst_alpha) + 0x80; + a = 255 - ((tmp + (tmp >> 8)) >> 8); + c = ((src_alpha << 16) + (a >> 1)) / a; +#else + tmp = art_rgba_composite_table[(src_alpha << 8) + dst_alpha]; + c = tmp & 0x1ffff; + a = tmp >> 24; +#endif +#ifdef WORDS_BIGENDIAN + src_r = (src_rgba >> 24) & 0xff; + src_g = (src_rgba >> 16) & 0xff; + src_b = (src_rgba >> 8) & 0xff; + dst_r = (dst_rgba >> 24) & 0xff; + dst_g = (dst_rgba >> 16) & 0xff; + dst_b = (dst_rgba >> 8) & 0xff; +#else + src_r = src_abgr & 0xff; + src_g = (src_abgr >> 8) & 0xff; + src_b = (src_abgr >> 16) & 0xff; + dst_r = dst_abgr & 0xff; + dst_g = (dst_abgr >> 8) & 0xff; + dst_b = (dst_abgr >> 16) & 0xff; +#endif + r = dst_r + (((src_r - dst_r) * c + 0x8000) >> 16); + g = dst_g + (((src_g - dst_g) * c + 0x8000) >> 16); + b = dst_b + (((src_b - dst_b) * c + 0x8000) >> 16); +#ifdef WORDS_BIGENDIAN + ((art_u32 *)dst)[i] = (r << 24) | (g << 16) | (b << 8) | a; +#else + ((art_u32 *)dst)[i] = (a << 24) | (b << 16) | (g << 8) | r; +#endif + } + } +#if 0 + /* it's not clear to me this optimization really wins */ + else + { + /* skip over run of transparent pixels */ + for (; i < n - 1; i++) + { +#ifdef WORDS_BIGENDIAN + src_rgba = ((art_u32 *)src)[i + 1]; + if (src_rgba & 0xff) + break; +#else + src_abgr = ((art_u32 *)src)[i + 1]; + if (src_abgr & 0xff000000) + break; +#endif + } + } +#endif + } +} + +/** + * art_rgba_fill_run: fill an RGBA buffer a solid RGB color. + * @buf: Buffer to fill. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @n: Number of RGB triples to fill. + * + * Fills a buffer with @n copies of the (@r, @g, @b) triple, solid + * alpha. Thus, locations @buf (inclusive) through @buf + 4 * @n + * (exclusive) are written. + **/ +void +art_rgba_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba; +#else + art_u32 src_abgr; +#endif + +#ifdef WORDS_BIGENDIAN + src_rgba = (r << 24) | (g << 16) | (b << 8) | 255; +#else + src_abgr = (255 << 24) | (b << 16) | (g << 8) | r; +#endif + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = src_rgba; +#else + ((art_u32 *)buf)[i] = src_abgr; +#endif + } +} + +/** + * art_rgba_run_alpha: Render semitransparent color over RGBA buffer. + * @buf: Buffer for rendering. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @alpha: Alpha, range 0..255. + * @n: Number of RGB triples to render. + * + * Renders a sequential run of solid (@r, @g, @b) color over @buf with + * opacity @alpha. Note that the range of @alpha is 0..255, in contrast + * to art_rgb_run_alpha, which has a range of 0..256. + **/ +void +art_rgba_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba, dst_rgba; +#else + art_u32 src_abgr, dst_abgr; +#endif + art_u8 dst_alpha; + int a; + int dst_r, dst_g, dst_b; + int tmp; + int c; + +#ifdef WORDS_BIGENDIAN + src_rgba = (r << 24) | (g << 16) | (b << 8) | alpha; +#else + src_abgr = (alpha << 24) | (b << 16) | (g << 8) | r; +#endif + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + dst_rgba = ((art_u32 *)buf)[i]; + dst_alpha = dst_rgba & 0xff; +#else + dst_abgr = ((art_u32 *)buf)[i]; + dst_alpha = (dst_abgr >> 24) & 0xff; +#endif + if (dst_alpha) + { +#ifdef ART_OPTIMIZE_SPACE + tmp = (255 - alpha) * (255 - dst_alpha) + 0x80; + a = 255 - ((tmp + (tmp >> 8)) >> 8); + c = ((alpha << 16) + (a >> 1)) / a; +#else + tmp = art_rgba_composite_table[(alpha << 8) + dst_alpha]; + c = tmp & 0x1ffff; + a = tmp >> 24; +#endif +#ifdef WORDS_BIGENDIAN + dst_r = (dst_rgba >> 24) & 0xff; + dst_g = (dst_rgba >> 16) & 0xff; + dst_b = (dst_rgba >> 8) & 0xff; +#else + dst_r = dst_abgr & 0xff; + dst_g = (dst_abgr >> 8) & 0xff; + dst_b = (dst_abgr >> 16) & 0xff; +#endif + dst_r += (((r - dst_r) * c + 0x8000) >> 16); + dst_g += (((g - dst_g) * c + 0x8000) >> 16); + dst_b += (((b - dst_b) * c + 0x8000) >> 16); +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = (dst_r << 24) | (dst_g << 16) | (dst_b << 8) | a; +#else + ((art_u32 *)buf)[i] = (a << 24) | (dst_b << 16) | (dst_g << 8) | dst_r; +#endif + } + else + { +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = src_rgba; +#else + ((art_u32 *)buf)[i] = src_abgr; +#endif + } + } +} diff --git a/libart_lgpl/art_rgba.h b/libart_lgpl/art_rgba.h new file mode 100644 index 0000000000..0d0fd432bd --- /dev/null +++ b/libart_lgpl/art_rgba.h @@ -0,0 +1,45 @@ +/* + * art_rgba.h: Functions for manipulating RGBA pixel data. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_RGBA_H__ +#define __ART_RGBA_H__ + +#include <libart_lgpl/art_misc.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void +art_rgba_rgba_composite (art_u8 *dst, const art_u8 *src, int n); + +void +art_rgba_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n); + +void +art_rgba_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/libart_lgpl/art_svp.c b/libart_lgpl/art_svp.c new file mode 100644 index 0000000000..8d7f7d143f --- /dev/null +++ b/libart_lgpl/art_svp.c @@ -0,0 +1,152 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for sorted vector paths */ + +#include "config.h" +#include "art_svp.h" + +#include "art_misc.h" + +/* Add a new segment. The arguments can be zero and NULL if the caller + would rather fill them in later. + + We also realloc one auxiliary array of ints of size n_segs if + desired. +*/ +/** + * art_svp_add_segment: Add a segment to an #ArtSVP structure. + * @p_vp: Pointer to where the #ArtSVP structure is stored. + * @pn_segs_max: Pointer to the allocated size of *@p_vp. + * @pn_points_max: Pointer to where auxiliary array is stored. + * @n_points: Number of points for new segment. + * @dir: Direction for new segment; 0 is up, 1 is down. + * @points: Points for new segment. + * @bbox: Bounding box for new segment. + * + * Adds a new segment to an ArtSVP structure. This routine reallocates + * the structure if necessary, updating *@p_vp and *@pn_segs_max as + * necessary. + * + * The new segment is simply added after all other segments. Thus, + * this routine should be called in order consistent with the #ArtSVP + * sorting rules. + * + * If the @bbox argument is given, it is simply stored in the new + * segment. Otherwise (if it is NULL), the bounding box is computed + * from the @points given. + **/ +int +art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max, + int **pn_points_max, + int n_points, int dir, ArtPoint *points, + ArtDRect *bbox) +{ + int seg_num; + ArtSVP *svp; + ArtSVPSeg *seg; + + svp = *p_vp; + seg_num = svp->n_segs++; + if (*pn_segs_max == seg_num) + { + *pn_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (*pn_segs_max - 1) * sizeof(ArtSVPSeg)); + *p_vp = svp; + if (pn_points_max != NULL) + *pn_points_max = art_renew (*pn_points_max, int, *pn_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = n_points; + seg->dir = dir; + seg->points = points; + if (bbox) + seg->bbox = *bbox; + else if (points) + { + double x_min, x_max; + int i; + + x_min = x_max = points[0].x; + for (i = 1; i < n_points; i++) + { + if (x_min > points[i].x) + x_min = points[i].x; + if (x_max < points[i].x) + x_max = points[i].x; + } + seg->bbox.x0 = x_min; + seg->bbox.y0 = points[0].y; + + seg->bbox.x1 = x_max; + seg->bbox.y1 = points[n_points - 1].y; + } + return seg_num; +} + + +/** + * art_svp_free: Free an #ArtSVP structure. + * @svp: #ArtSVP to free. + * + * Frees an #ArtSVP structure and all the segments in it. + **/ +void +art_svp_free (ArtSVP *svp) +{ + int n_segs = svp->n_segs; + int i; + + for (i = 0; i < n_segs; i++) + art_free (svp->segs[i].points); + art_free (svp); +} + +#ifdef ART_USE_NEW_INTERSECTOR +#define EPSILON 0 +#else +#define EPSILON 1e-6 +#endif + +/** + * art_svp_seg_compare: Compare two segments of an svp. + * @seg1: First segment to compare. + * @seg2: Second segment to compare. + * + * Compares two segments of an svp. Return 1 if @seg2 is below or to the + * right of @seg1, -1 otherwise. + **/ +int +art_svp_seg_compare (const void *s1, const void *s2) +{ + const ArtSVPSeg *seg1 = s1; + const ArtSVPSeg *seg2 = s2; + + if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1; + else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1; + else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1; + else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1; + else if ((seg1->points[1].x - seg1->points[0].x) * + (seg2->points[1].y - seg2->points[0].y) - + (seg1->points[1].y - seg1->points[0].y) * + (seg2->points[1].x - seg2->points[0].x) > 0) return 1; + else return -1; +} + diff --git a/libart_lgpl/art_svp.h b/libart_lgpl/art_svp.h new file mode 100644 index 0000000000..c898c7a2ec --- /dev/null +++ b/libart_lgpl/art_svp.h @@ -0,0 +1,63 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_H__ +#define __ART_SVP_H__ + +/* Basic data structures and constructors for sorted vector paths */ + +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_point.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtSVP ArtSVP; +typedef struct _ArtSVPSeg ArtSVPSeg; + +struct _ArtSVPSeg { + int n_points; + int dir; /* == 0 for "up", 1 for "down" */ + ArtDRect bbox; + ArtPoint *points; +}; + +struct _ArtSVP { + int n_segs; + ArtSVPSeg segs[1]; +}; + +int +art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max, + int **pn_points_max, + int n_points, int dir, ArtPoint *points, + ArtDRect *bbox); + +void +art_svp_free (ArtSVP *svp); + +int +art_svp_seg_compare (const void *s1, const void *s2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_H__ */ diff --git a/libart_lgpl/art_svp_intersect.c b/libart_lgpl/art_svp_intersect.c new file mode 100644 index 0000000000..4ece5f4643 --- /dev/null +++ b/libart_lgpl/art_svp_intersect.c @@ -0,0 +1,1803 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2001 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This file contains a testbed implementation of the new intersection + code. +*/ + +#include "config.h" +#include "art_svp_intersect.h" + +#include <math.h> /* for sqrt */ + +/* Sanitychecking verifies the main invariant on every priority queue + point. Do not use in production, as it slows things down way too + much. */ +#define noSANITYCHECK + +/* This can be used in production, to prevent hangs. Eventually, it + should not be necessary. */ +#define CHEAP_SANITYCHECK + +#define noVERBOSE + +#include "art_misc.h" + +/* A priority queue - perhaps move to a separate file if it becomes + needed somewhere else */ + +#define ART_PRIQ_USE_HEAP + +typedef struct _ArtPriQ ArtPriQ; +typedef struct _ArtPriPoint ArtPriPoint; + +struct _ArtPriQ { + int n_items; + int n_items_max; + ArtPriPoint **items; +}; + +struct _ArtPriPoint { + double x; + double y; + void *user_data; +}; + +static ArtPriQ * +art_pri_new (void) +{ + ArtPriQ *result = art_new (ArtPriQ, 1); + + result->n_items = 0; + result->n_items_max = 16; + result->items = art_new (ArtPriPoint *, result->n_items_max); + return result; +} + +static void +art_pri_free (ArtPriQ *pq) +{ + art_free (pq->items); + art_free (pq); +} + +static art_boolean +art_pri_empty (ArtPriQ *pq) +{ + return pq->n_items == 0; +} + +#ifdef ART_PRIQ_USE_HEAP + +/* This heap implementation is based on Vasek Chvatal's course notes: + http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */ + +static void +art_pri_bubble_up (ArtPriQ *pq, int vacant, ArtPriPoint *missing) +{ + ArtPriPoint **items = pq->items; + int parent; + + parent = (vacant - 1) >> 1; + while (vacant > 0 && (missing->y < items[parent]->y || + (missing->y == items[parent]->y && + missing->x < items[parent]->x))) + { + items[vacant] = items[parent]; + vacant = parent; + parent = (vacant - 1) >> 1; + } + + items[vacant] = missing; +} + +static void +art_pri_insert (ArtPriQ *pq, ArtPriPoint *point) +{ + if (pq->n_items == pq->n_items_max) + art_expand (pq->items, ArtPriPoint *, pq->n_items_max); + + art_pri_bubble_up (pq, pq->n_items++, point); +} + +static void +art_pri_sift_down_from_root (ArtPriQ *pq, ArtPriPoint *missing) +{ + ArtPriPoint **items = pq->items; + int vacant = 0, child = 2; + int n = pq->n_items; + + while (child < n) + { + if (items[child - 1]->y < items[child]->y || + (items[child - 1]->y == items[child]->y && + items[child - 1]->x < items[child]->x)) + child--; + items[vacant] = items[child]; + vacant = child; + child = (vacant + 1) << 1; + } + if (child == n) + { + items[vacant] = items[n - 1]; + vacant = n - 1; + } + + art_pri_bubble_up (pq, vacant, missing); +} + +static ArtPriPoint * +art_pri_choose (ArtPriQ *pq) +{ + ArtPriPoint *result = pq->items[0]; + + art_pri_sift_down_from_root (pq, pq->items[--pq->n_items]); + return result; +} + +#else + +/* Choose least point in queue */ +static ArtPriPoint * +art_pri_choose (ArtPriQ *pq) +{ + int i; + int best = 0; + double best_x, best_y; + double y; + ArtPriPoint *result; + + if (pq->n_items == 0) + return NULL; + + best_x = pq->items[best]->x; + best_y = pq->items[best]->y; + + for (i = 1; i < pq->n_items; i++) + { + y = pq->items[i]->y; + if (y < best_y || (y == best_y && pq->items[i]->x < best_x)) + { + best = i; + best_x = pq->items[best]->x; + best_y = y; + } + } + result = pq->items[best]; + pq->items[best] = pq->items[--pq->n_items]; + return result; +} + +static void +art_pri_insert (ArtPriQ *pq, ArtPriPoint *point) +{ + if (pq->n_items == pq->n_items_max) + art_expand (pq->items, ArtPriPoint *, pq->n_items_max); + + pq->items[pq->n_items++] = point; +} + +#endif + +#ifdef TEST_PRIQ + +#include <stdlib.h> /* for rand() */ +#include <stdio.h> + +static double +double_rand (double lo, double hi, int quant) +{ + int tmp = rand () / (RAND_MAX * (1.0 / quant)) + 0.5; + return lo + tmp * ((hi - lo) / quant); +} + +/* + * This custom allocator for priority queue points is here so I can + * test speed. It doesn't look like it will be that significant, but + * if I want a small improvement later, it's something. + */ + +typedef ArtPriPoint *ArtPriPtPool; + +static ArtPriPtPool * +art_pri_pt_pool_new (void) +{ + ArtPriPtPool *result = art_new (ArtPriPtPool, 1); + *result = NULL; + return result; +} + +static ArtPriPoint * +art_pri_pt_alloc (ArtPriPtPool *pool) +{ + ArtPriPoint *result = *pool; + if (result == NULL) + return art_new (ArtPriPoint, 1); + else + { + *pool = result->user_data; + return result; + } +} + +static void +art_pri_pt_free (ArtPriPtPool *pool, ArtPriPoint *pt) +{ + pt->user_data = *pool; + *pool = pt; +} + +static void +art_pri_pt_pool_free (ArtPriPtPool *pool) +{ + ArtPriPoint *pt = *pool; + while (pt != NULL) + { + ArtPriPoint *next = pt->user_data; + art_free (pt); + pt = next; + } + art_free (pool); +} + +int +main (int argc, char **argv) +{ + ArtPriPtPool *pool = art_pri_pt_pool_new (); + ArtPriQ *pq; + int i, j; + const int n_iter = 1; + const int pq_size = 100; + + for (j = 0; j < n_iter; j++) + { + pq = art_pri_new (); + + for (i = 0; i < pq_size; i++) + { + ArtPriPoint *pt = art_pri_pt_alloc (pool); + pt->x = double_rand (0, 1, 100); + pt->y = double_rand (0, 1, 100); + pt->user_data = (void *)i; + art_pri_insert (pq, pt); + } + + while (!art_pri_empty (pq)) + { + ArtPriPoint *pt = art_pri_choose (pq); + if (n_iter == 1) + printf ("(%g, %g), %d\n", pt->x, pt->y, (int)pt->user_data); + art_pri_pt_free (pool, pt); + } + + art_pri_free (pq); + } + art_pri_pt_pool_free (pool); + return 0; +} + +#else /* TEST_PRIQ */ + +/* A virtual class for an "svp writer". A client of this object creates an + SVP by repeatedly calling "add segment" and "add point" methods on it. +*/ + +typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind; + +/* An implementation of the svp writer virtual class that applies the + winding rule. */ + +struct _ArtSvpWriterRewind { + ArtSvpWriter super; + ArtWindRule rule; + ArtSVP *svp; + int n_segs_max; + int *n_points_max; +}; + +static int +art_svp_writer_rewind_add_segment (ArtSvpWriter *self, int wind_left, + int delta_wind, double x, double y) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *svp; + ArtSVPSeg *seg; + art_boolean left_filled, right_filled; + int wind_right = wind_left + delta_wind; + int seg_num; + const int init_n_points_max = 4; + + switch (swr->rule) + { + case ART_WIND_RULE_NONZERO: + left_filled = (wind_left != 0); + right_filled = (wind_right != 0); + break; + case ART_WIND_RULE_INTERSECT: + left_filled = (wind_left > 1); + right_filled = (wind_right > 1); + break; + case ART_WIND_RULE_ODDEVEN: + left_filled = (wind_left & 1); + right_filled = (wind_right & 1); + break; + case ART_WIND_RULE_POSITIVE: + left_filled = (wind_left > 0); + right_filled = (wind_right > 0); + break; + default: + art_die ("Unknown wind rule %d\n", swr->rule); + } + if (left_filled == right_filled) + { + /* discard segment now */ +#ifdef VERBOSE + art_dprint ("swr add_segment: %d += %d (%g, %g) --> -1\n", + wind_left, delta_wind, x, y); +#endif + return -1; + } + + svp = swr->svp; + seg_num = svp->n_segs++; + if (swr->n_segs_max == seg_num) + { + swr->n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (swr->n_segs_max - 1) * + sizeof(ArtSVPSeg)); + swr->svp = svp; + swr->n_points_max = art_renew (swr->n_points_max, int, + swr->n_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = 1; + seg->dir = right_filled; + swr->n_points_max[seg_num] = init_n_points_max; + seg->bbox.x0 = x; + seg->bbox.y0 = y; + seg->bbox.x1 = x; + seg->bbox.y1 = y; + seg->points = art_new (ArtPoint, init_n_points_max); + seg->points[0].x = x; + seg->points[0].y = y; +#ifdef VERBOSE + art_dprint ("swr add_segment: %d += %d (%g, %g) --> %d(%s)\n", + wind_left, delta_wind, x, y, seg_num, + seg->dir ? "v" : "^"); +#endif + return seg_num; +} + +static void +art_svp_writer_rewind_add_point (ArtSvpWriter *self, int seg_id, + double x, double y) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + int n_points; + +#ifdef VERBOSE + art_dprint ("swr add_point: %d (%g, %g)\n", seg_id, x, y); +#endif + if (seg_id < 0) + /* omitted segment */ + return; + + seg = &swr->svp->segs[seg_id]; + n_points = seg->n_points++; + if (swr->n_points_max[seg_id] == n_points) + art_expand (seg->points, ArtPoint, swr->n_points_max[seg_id]); + seg->points[n_points].x = x; + seg->points[n_points].y = y; + if (x < seg->bbox.x0) + seg->bbox.x0 = x; + if (x > seg->bbox.x1) + seg->bbox.x1 = x; + seg->bbox.y1 = y; +} + +static void +art_svp_writer_rewind_close_segment (ArtSvpWriter *self, int seg_id) +{ + /* Not needed for this simple implementation. A potential future + optimization is to merge segments that can be merged safely. */ +#ifdef SANITYCHECK + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + + if (seg_id >= 0) + { + seg = &swr->svp->segs[seg_id]; + if (seg->n_points < 2) + art_warn ("*** closing segment %d with only %d point%s\n", + seg_id, seg->n_points, seg->n_points == 1 ? "" : "s"); + } +#endif + +#ifdef VERBOSE + art_dprint ("swr close_segment: %d\n", seg_id); +#endif +} + +ArtSVP * +art_svp_writer_rewind_reap (ArtSvpWriter *self) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *result = swr->svp; + + art_free (swr->n_points_max); + art_free (swr); + return result; +} + +ArtSvpWriter * +art_svp_writer_rewind_new (ArtWindRule rule) +{ + ArtSvpWriterRewind *result = art_new (ArtSvpWriterRewind, 1); + + result->super.add_segment = art_svp_writer_rewind_add_segment; + result->super.add_point = art_svp_writer_rewind_add_point; + result->super.close_segment = art_svp_writer_rewind_close_segment; + + result->rule = rule; + result->n_segs_max = 16; + result->svp = art_alloc (sizeof(ArtSVP) + + (result->n_segs_max - 1) * sizeof(ArtSVPSeg)); + result->svp->n_segs = 0; + result->n_points_max = art_new (int, result->n_segs_max); + + return &result->super; +} + +/* Now, data structures for the active list */ + +typedef struct _ArtActiveSeg ArtActiveSeg; + +/* Note: BNEG is 1 for \ lines, and 0 for /. Thus, + x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */ +#define ART_ACTIVE_FLAGS_BNEG 1 + +/* This flag is set if the segment has been inserted into the active + list. */ +#define ART_ACTIVE_FLAGS_IN_ACTIVE 2 + +/* This flag is set when the segment is to be deleted in the + horiz commit process. */ +#define ART_ACTIVE_FLAGS_DEL 4 + +/* This flag is set if the seg_id is a valid output segment. */ +#define ART_ACTIVE_FLAGS_OUT 8 + +/* This flag is set if the segment is in the horiz list. */ +#define ART_ACTIVE_FLAGS_IN_HORIZ 16 + +struct _ArtActiveSeg { + int flags; + int wind_left, delta_wind; + ArtActiveSeg *left, *right; /* doubly linked list structure */ + + const ArtSVPSeg *in_seg; + int in_curs; + + double x[2]; + double y0, y1; + double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1, + and a>0 */ + + /* bottom point and intersection point stack */ + int n_stack; + int n_stack_max; + ArtPoint *stack; + + /* horiz commit list */ + ArtActiveSeg *horiz_left, *horiz_right; + double horiz_x; + int horiz_delta_wind; + int seg_id; +}; + +typedef struct _ArtIntersectCtx ArtIntersectCtx; + +struct _ArtIntersectCtx { + const ArtSVP *in; + ArtSvpWriter *out; + + ArtPriQ *pq; + + ArtActiveSeg *active_head; + + double y; + ArtActiveSeg *horiz_first; + ArtActiveSeg *horiz_last; + + /* segment index of next input segment to be added to pri q */ + int in_curs; +}; + +#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */ + +/** + * art_svp_intersect_setup_seg: Set up an active segment from input segment. + * @seg: Active segment. + * @pri_pt: Priority queue point to initialize. + * + * Sets the x[], a, b, c, flags, and stack fields according to the + * line from the current cursor value. Sets the priority queue point + * to the bottom point of this line. Also advances the input segment + * cursor. + **/ +static void +art_svp_intersect_setup_seg (ArtActiveSeg *seg, ArtPriPoint *pri_pt) +{ + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs++; + double x0, y0, x1, y1; + double dx, dy, s; + double a, b, r2; + + x0 = in_seg->points[in_curs].x; + y0 = in_seg->points[in_curs].y; + x1 = in_seg->points[in_curs + 1].x; + y1 = in_seg->points[in_curs + 1].y; + pri_pt->x = x1; + pri_pt->y = y1; + dx = x1 - x0; + dy = y1 - y0; + r2 = dx * dx + dy * dy; + s = r2 == 0 ? 1 : 1 / sqrt (r2); + seg->a = a = dy * s; + seg->b = b = -dx * s; + seg->c = -(a * x0 + b * y0); + seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0); + seg->x[0] = x0; + seg->x[1] = x1; + seg->y0 = y0; + seg->y1 = y1; + seg->n_stack = 1; + seg->stack[0].x = x1; + seg->stack[0].y = y1; +} + +/** + * art_svp_intersect_add_horiz: Add point to horizontal list. + * @ctx: Intersector context. + * @seg: Segment with point to insert into horizontal list. + * + * Inserts @seg into horizontal list, keeping it in ascending horiz_x + * order. + * + * Note: the horiz_commit routine processes "clusters" of segs in the + * horiz list, all sharing the same horiz_x value. The cluster is + * processed in active list order, rather than horiz list order. Thus, + * the order of segs in the horiz list sharing the same horiz_x + * _should_ be irrelevant. Even so, we use b as a secondary sorting key, + * as a "belt and suspenders" defensive coding tactic. + **/ +static void +art_svp_intersect_add_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + ArtActiveSeg **pp = &ctx->horiz_last; + ArtActiveSeg *place; + ArtActiveSeg *place_right = NULL; + + +#ifdef CHEAP_SANITYCHECK + if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ) + { + art_warn ("*** attempt to put segment in horiz list twice\n"); + return; + } + seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ; +#endif + +#ifdef VERBOSE + art_dprint ("add_horiz %lx, x = %g\n", (unsigned long) seg, seg->horiz_x); +#endif + for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x || + (place->horiz_x == seg->horiz_x && + place->b < seg->b)); + place = *pp) + { + place_right = place; + pp = &place->horiz_left; + } + *pp = seg; + seg->horiz_left = place; + seg->horiz_right = place_right; + if (place == NULL) + ctx->horiz_first = seg; + else + place->horiz_right = seg; +} + +static void +art_svp_intersect_push_pt (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x, double y) +{ + ArtPriPoint *pri_pt; + int n_stack = seg->n_stack; + + if (n_stack == seg->n_stack_max) + art_expand (seg->stack, ArtPoint, seg->n_stack_max); + seg->stack[n_stack].x = x; + seg->stack[n_stack].y = y; + seg->n_stack++; + + seg->x[1] = x; + seg->y1 = y; + + pri_pt = art_new (ArtPriPoint, 1); + pri_pt->x = x; + pri_pt->y = y; + pri_pt->user_data = seg; + art_pri_insert (ctx->pq, pri_pt); +} + +typedef enum { + ART_BREAK_LEFT = 1, + ART_BREAK_RIGHT = 2 +} ArtBreakFlags; + +/** + * art_svp_intersect_break: Break an active segment. + * + * Note: y must be greater than the top point's y, and less than + * the bottom's. + * + * Return value: x coordinate of break point. + */ +static double +art_svp_intersect_break (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x_ref, double y, ArtBreakFlags break_flags) +{ + double x0, y0, x1, y1; + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + double x; + + x0 = in_seg->points[in_curs - 1].x; + y0 = in_seg->points[in_curs - 1].y; + x1 = in_seg->points[in_curs].x; + y1 = in_seg->points[in_curs].y; + x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0)); + if ((break_flags == ART_BREAK_LEFT && x > x_ref) || + (break_flags == ART_BREAK_RIGHT && x < x_ref)) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_break: limiting x to %f, was %f, %s\n", + x_ref, x, break_flags == ART_BREAK_LEFT ? "left" : "right"); + x = x_ref; +#endif + } + + /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane + arithmetic, but it might be worthwhile to check just in case. */ + + if (y > ctx->y) + art_svp_intersect_push_pt (ctx, seg, x, y); + else + { + seg->x[0] = x; + seg->y0 = y; + seg->horiz_x = x; + art_svp_intersect_add_horiz (ctx, seg); + } + + return x; +} + +/** + * art_svp_intersect_add_point: Add a point, breaking nearby neighbors. + * @ctx: Intersector context. + * @x: X coordinate of point to add. + * @y: Y coordinate of point to add. + * @seg: "nearby" segment, or NULL if leftmost. + * + * Return value: Segment immediately to the left of the new point, or + * NULL if the new point is leftmost. + **/ +static ArtActiveSeg * +art_svp_intersect_add_point (ArtIntersectCtx *ctx, double x, double y, + ArtActiveSeg *seg, ArtBreakFlags break_flags) +{ + ArtActiveSeg *left, *right; + double x_min = x, x_max = x; + art_boolean left_live, right_live; + double d; + double new_x; + ArtActiveSeg *test, *result = NULL; + double x_test; + + left = seg; + if (left == NULL) + right = ctx->active_head; + else + right = left->right; + left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL); + right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL); + while (left_live || right_live) + { + if (left_live) + { + if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != left->y0 && y < left->y1) + { + d = x_min * left->a + y * left->b + left->c; + if (d < EPSILON_A) + { + new_x = art_svp_intersect_break (ctx, left, x_min, y, + ART_BREAK_LEFT); + if (new_x > x_max) + { + x_max = new_x; + right_live = (right != NULL); + } + else if (new_x < x_min) + x_min = new_x; + left = left->left; + left_live = (left != NULL); + } + else + left_live = ART_FALSE; + } + else + left_live = ART_FALSE; + } + else if (right_live) + { + if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != right->y0 && y < right->y1) + { + d = x_max * right->a + y * right->b + right->c; + if (d > -EPSILON_A) + { + new_x = art_svp_intersect_break (ctx, right, x_max, y, + ART_BREAK_RIGHT); + if (new_x < x_min) + { + x_min = new_x; + left_live = (left != NULL); + } + else if (new_x >= x_max) + x_max = new_x; + right = right->right; + right_live = (right != NULL); + } + else + right_live = ART_FALSE; + } + else + right_live = ART_FALSE; + } + } + + /* Ascending order is guaranteed by break_flags. Thus, we don't need + to actually fix up non-ascending pairs. */ + + /* Now, (left, right) defines an interval of segments broken. Sort + into ascending x order. */ + test = left == NULL ? ctx->active_head : left->right; + result = left; + if (test != NULL && test != right) + { + if (y == test->y0) + x_test = test->x[0]; + else /* assert y == test->y1, I think */ + x_test = test->x[1]; + for (;;) + { + if (x_test <= x) + result = test; + test = test->right; + if (test == right) + break; + new_x = x_test; + if (new_x < x_test) + { + art_warn ("art_svp_intersect_add_point: non-ascending x\n"); + } + x_test = new_x; + } + } + return result; +} + +static void +art_svp_intersect_swap_active (ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg) +{ + right_seg->left = left_seg->left; + if (right_seg->left != NULL) + right_seg->left->right = right_seg; + else + ctx->active_head = right_seg; + left_seg->right = right_seg->right; + if (left_seg->right != NULL) + left_seg->right->left = left_seg; + left_seg->left = right_seg; + right_seg->right = left_seg; +} + +/** + * art_svp_intersect_test_cross: Test crossing of a pair of active segments. + * @ctx: Intersector context. + * @left_seg: Left segment of the pair. + * @right_seg: Right segment of the pair. + * @break_flags: Flags indicating whether to break neighbors. + * + * Tests crossing of @left_seg and @right_seg. If there is a crossing, + * inserts the intersection point into both segments. + * + * Return value: True if the intersection took place at the current + * scan line, indicating further iteration is needed. + **/ +static art_boolean +art_svp_intersect_test_cross (ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg, + ArtBreakFlags break_flags) +{ + double left_x0, left_y0, left_x1; + double left_y1 = left_seg->y1; + double right_y1 = right_seg->y1; + double d; + + const ArtSVPSeg *in_seg; + int in_curs; + double d0, d1, t; + double x, y; /* intersection point */ + +#ifdef VERBOSE + static int count = 0; + + art_dprint ("art_svp_intersect_test_cross %lx <-> %lx: count=%d\n", + (unsigned long)left_seg, (unsigned long)right_seg, count++); +#endif + + if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0]) + { + /* Top points of left and right segments coincide. This case + feels like a bit of duplication - we may want to merge it + with the cases below. However, this way, we're sure that this + logic makes only localized changes. */ + + if (left_y1 < right_y1) + { + /* Test left (x1, y1) against right segment */ + double left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) + { + /* I'm unsure about the break flags here. */ + double right_x1 = art_svp_intersect_break (ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else if (left_y1 > right_y1) + { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) + { + /* See above regarding break flags. */ + double left_x1 = art_svp_intersect_break (ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else /* left_y1 == right_y1 */ + { + double left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + art_svp_intersect_swap_active (ctx, left_seg, right_seg); + return ART_TRUE; + } + + if (left_y1 < right_y1) + { + /* Test left (x1, y1) against right segment */ + double left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) + { + double right_x1 = art_svp_intersect_break (ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else if (left_y1 > right_y1) + { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) + { + double left_x1 = art_svp_intersect_break (ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else /* left_y1 == right_y1 */ + { + double left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + + /* The segments cross. Find the intersection point. */ + + in_seg = left_seg->in_seg; + in_curs = left_seg->in_curs; + left_x0 = in_seg->points[in_curs - 1].x; + left_y0 = in_seg->points[in_curs - 1].y; + left_x1 = in_seg->points[in_curs].x; + left_y1 = in_seg->points[in_curs].y; + d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c; + d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d0 == d1) + { + x = left_x0; + y = left_y0; + } + else + { + /* Is this division always safe? It could possibly overflow. */ + t = d0 / (d0 - d1); + if (t <= 0) + { + x = left_x0; + y = left_y0; + } + else if (t >= 1) + { + x = left_x1; + y = left_y1; + } + else + { + x = left_x0 + t * (left_x1 - left_x0); + y = left_y0 + t * (left_y1 - left_y0); + } + } + + /* Make sure intersection point is within bounds of right seg. */ + if (y < right_seg->y0) + { + x = right_seg->x[0]; + y = right_seg->y0; + } + else if (y > right_seg->y1) + { + x = right_seg->x[1]; + y = right_seg->y1; + } + else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) + x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]; + else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]) + x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]; + + if (y == left_seg->y0) + { + if (y != right_seg->y0) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_test_cross: intersection (%g, %g) matches former y0 of %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + art_svp_intersect_push_pt (ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point (ctx, x, y, right_seg->right, + break_flags); + } + else + { + /* Intersection takes place at current scan line; process + immediately rather than queueing intersection point into + priq. */ + ArtActiveSeg *winner, *loser; + + /* Choose "most vertical" segement */ + if (left_seg->a > right_seg->a) + { + winner = left_seg; + loser = right_seg; + } + else + { + winner = right_seg; + loser = left_seg; + } + + loser->x[0] = winner->x[0]; + loser->horiz_x = loser->x[0]; + loser->horiz_delta_wind += loser->delta_wind; + winner->horiz_delta_wind -= loser->delta_wind; + + art_svp_intersect_swap_active (ctx, left_seg, right_seg); + return ART_TRUE; + } + } + else if (y == right_seg->y0) + { +#ifdef VERBOSE + art_dprint ("*** art_svp_intersect_test_cross: intersection (%g, %g) matches latter y0 of %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + art_svp_intersect_push_pt (ctx, left_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point (ctx, x, y, left_seg->left, + break_flags); + } + else + { +#ifdef VERBOSE + art_dprint ("Inserting (%g, %g) into %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + /* Insert the intersection point into both segments. */ + art_svp_intersect_push_pt (ctx, left_seg, x, y); + art_svp_intersect_push_pt (ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point (ctx, x, y, left_seg->left, break_flags); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point (ctx, x, y, right_seg->right, break_flags); + } + return ART_FALSE; +} + +/** + * art_svp_intersect_active_delete: Delete segment from active list. + * @ctx: Intersection context. + * @seg: Segment to delete. + * + * Deletes @seg from the active list. + **/ +static /* todo inline */ void +art_svp_intersect_active_delete (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + ArtActiveSeg *left = seg->left, *right = seg->right; + + if (left != NULL) + left->right = right; + else + ctx->active_head = right; + if (right != NULL) + right->left = left; +} + +/** + * art_svp_intersect_active_free: Free an active segment. + * @seg: Segment to delete. + * + * Frees @seg. + **/ +static /* todo inline */ void +art_svp_intersect_active_free (ArtActiveSeg *seg) +{ + art_free (seg->stack); +#ifdef VERBOSE + art_dprint ("Freeing %lx\n", (unsigned long) seg); +#endif + art_free (seg); +} + +/** + * art_svp_intersect_insert_cross: Test crossings of newly inserted line. + * + * Tests @seg against its left and right neighbors for intersections. + * Precondition: the line in @seg is not purely horizontal. + **/ +static void +art_svp_intersect_insert_cross (ArtIntersectCtx *ctx, + ArtActiveSeg *seg) +{ + ArtActiveSeg *left = seg, *right = seg; + + for (;;) + { + if (left != NULL) + { + ArtActiveSeg *leftc; + + for (leftc = left->left; leftc != NULL; leftc = leftc->left) + if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (leftc != NULL && + art_svp_intersect_test_cross (ctx, leftc, left, + ART_BREAK_LEFT)) + { + if (left == right || right == NULL) + right = left->right; + } + else + { + left = NULL; + } + } + else if (right != NULL && right->right != NULL) + { + ArtActiveSeg *rightc; + + for (rightc = right->right; rightc != NULL; rightc = rightc->right) + if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (rightc != NULL && + art_svp_intersect_test_cross (ctx, right, rightc, + ART_BREAK_RIGHT)) + { + if (left == right || left == NULL) + left = right->left; + } + else + { + right = NULL; + } + } + else + break; + } +} + +/** + * art_svp_intersect_horiz: Add horizontal line segment. + * @ctx: Intersector context. + * @seg: Segment on which to add horizontal line. + * @x0: Old x position. + * @x1: New x position. + * + * Adds a horizontal line from @x0 to @x1, and updates the current + * location of @seg to @x1. + **/ +static void +art_svp_intersect_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x0, double x1) +{ + ArtActiveSeg *hs; + + if (x0 == x1) + return; + + hs = art_new (ArtActiveSeg, 1); + + hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT); + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + ArtSvpWriter *swr = ctx->out; + + swr->add_point (swr, seg->seg_id, x0, ctx->y); + } + hs->seg_id = seg->seg_id; + hs->horiz_x = x0; + hs->horiz_delta_wind = seg->delta_wind; + hs->stack = NULL; + + /* Ideally, the (a, b, c) values will never be read. However, there + are probably some tests remaining that don't check for _DEL + before evaluating the line equation. For those, these + initializations will at least prevent a UMR of the values, which + can crash on some platforms. */ + hs->a = 0.0; + hs->b = 0.0; + hs->c = 0.0; + + seg->horiz_delta_wind -= seg->delta_wind; + + art_svp_intersect_add_horiz (ctx, hs); + + if (x0 > x1) + { + ArtActiveSeg *left; + art_boolean first = ART_TRUE; + + for (left = seg->left; left != NULL; left = seg->left) + { + int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG; + + if (left->x[left_bneg] <= x1) + break; + if (left->x[left_bneg ^ 1] <= x1 && + x1 * left->a + ctx->y * left->b + left->c >= 0) + break; + if (left->y0 != ctx->y && left->y1 != ctx->y) + { + art_svp_intersect_break (ctx, left, x1, ctx->y, ART_BREAK_LEFT); + } +#ifdef VERBOSE + art_dprint ("x0=%g > x1=%g, swapping %lx, %lx\n", + x0, x1, (unsigned long)left, (unsigned long)seg); +#endif + art_svp_intersect_swap_active (ctx, left, seg); + if (first && left->right != NULL) + { + art_svp_intersect_test_cross (ctx, left, left->right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + else + { + ArtActiveSeg *right; + art_boolean first = ART_TRUE; + + for (right = seg->right; right != NULL; right = seg->right) + { + int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG; + + if (right->x[right_bneg ^ 1] >= x1) + break; + if (right->x[right_bneg] >= x1 && + x1 * right->a + ctx->y * right->b + right->c <= 0) + break; + if (right->y0 != ctx->y && right->y1 != ctx->y) + { + art_svp_intersect_break (ctx, right, x1, ctx->y, + ART_BREAK_LEFT); + } +#ifdef VERBOSE + art_dprint ("[right]x0=%g < x1=%g, swapping %lx, %lx\n", + x0, x1, (unsigned long)seg, (unsigned long)right); +#endif + art_svp_intersect_swap_active (ctx, seg, right); + if (first && right->left != NULL) + { + art_svp_intersect_test_cross (ctx, right->left, right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + + seg->x[0] = x1; + seg->x[1] = x1; + seg->horiz_x = x1; + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +} + +/** + * art_svp_intersect_insert_line: Insert a line into the active list. + * @ctx: Intersector context. + * @seg: Segment containing line to insert. + * + * Inserts the line into the intersector context, taking care of any + * intersections, and adding the appropriate horizontal points to the + * active list. + **/ +static void +art_svp_intersect_insert_line (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + if (seg->y1 == seg->y0) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_insert_line: %lx is horizontal\n", + (unsigned long)seg); +#endif + art_svp_intersect_horiz (ctx, seg, seg->x[0], seg->x[1]); + } + else + { + art_svp_intersect_insert_cross (ctx, seg); + art_svp_intersect_add_horiz (ctx, seg); + } +} + +static void +art_svp_intersect_process_intersection (ArtIntersectCtx *ctx, + ArtActiveSeg *seg) +{ + int n_stack = --seg->n_stack; + seg->x[1] = seg->stack[n_stack - 1].x; + seg->y1 = seg->stack[n_stack - 1].y; + seg->x[0] = seg->stack[n_stack].x; + seg->y0 = seg->stack[n_stack].y; + seg->horiz_x = seg->x[0]; + art_svp_intersect_insert_line (ctx, seg); +} + +static void +art_svp_intersect_advance_cursor (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + ArtPriPoint *pri_pt) +{ + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL; + + if (swr != NULL) + swr->add_point (swr, seg->seg_id, seg->x[1], seg->y1); + if (in_curs + 1 == in_seg->n_points) + { + ArtActiveSeg *left = seg->left, *right = seg->right; + +#if 0 + if (swr != NULL) + swr->close_segment (swr, seg->seg_id); + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +#endif + seg->flags |= ART_ACTIVE_FLAGS_DEL; + art_svp_intersect_add_horiz (ctx, seg); + art_svp_intersect_active_delete (ctx, seg); + if (left != NULL && right != NULL) + art_svp_intersect_test_cross (ctx, left, right, + ART_BREAK_LEFT | ART_BREAK_RIGHT); + art_free (pri_pt); + } + else + { + seg->horiz_x = seg->x[1]; + + art_svp_intersect_setup_seg (seg, pri_pt); + art_pri_insert (ctx->pq, pri_pt); + art_svp_intersect_insert_line (ctx, seg); + } +} + +static void +art_svp_intersect_add_seg (ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg) +{ + ArtActiveSeg *seg = art_new (ArtActiveSeg, 1); + ArtActiveSeg *test; + double x0, y0; + ArtActiveSeg *beg_range; + ArtActiveSeg *last = NULL; + ArtActiveSeg *left, *right; + ArtPriPoint *pri_pt = art_new (ArtPriPoint, 1); + + seg->flags = 0; + seg->in_seg = in_seg; + seg->in_curs = 0; + + seg->n_stack_max = 4; + seg->stack = art_new (ArtPoint, seg->n_stack_max); + + seg->horiz_delta_wind = 0; + + seg->wind_left = 0; + + pri_pt->user_data = seg; + art_svp_intersect_setup_seg (seg, pri_pt); + art_pri_insert (ctx->pq, pri_pt); + + /* Find insertion place for new segment */ + /* This is currently a left-to-right scan, but should be replaced + with a binary search as soon as it's validated. */ + + x0 = in_seg->points[0].x; + y0 = in_seg->points[0].y; + beg_range = NULL; + for (test = ctx->active_head; test != NULL; test = test->right) + { + double d; + int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG; + + if (x0 < test->x[test_bneg]) + { + if (x0 < test->x[test_bneg ^ 1]) + break; + d = x0 * test->a + y0 * test->b + test->c; + if (d < 0) + break; + } + last = test; + } + + left = art_svp_intersect_add_point (ctx, x0, y0, last, ART_BREAK_LEFT | ART_BREAK_RIGHT); + seg->left = left; + if (left == NULL) + { + right = ctx->active_head; + ctx->active_head = seg; + } + else + { + right = left->right; + left->right = seg; + } + seg->right = right; + if (right != NULL) + right->left = seg; + + seg->delta_wind = in_seg->dir ? 1 : -1; + seg->horiz_x = x0; + + art_svp_intersect_insert_line (ctx, seg); +} + +#ifdef SANITYCHECK +static void +art_svp_intersect_sanitycheck_winding (ArtIntersectCtx *ctx) +{ +#if 0 + /* At this point, we seem to be getting false positives, so it's + turned off for now. */ + + ArtActiveSeg *seg; + int winding_number = 0; + + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + /* Check winding number consistency. */ + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + if (winding_number != seg->wind_left) + art_warn ("*** art_svp_intersect_sanitycheck_winding: seg %lx has wind_left of %d, expected %d\n", + (unsigned long) seg, seg->wind_left, winding_number); + winding_number = seg->wind_left + seg->delta_wind; + } + } + if (winding_number != 0) + art_warn ("*** art_svp_intersect_sanitycheck_winding: non-balanced winding number %d\n", + winding_number); +#endif +} +#endif + +/** + * art_svp_intersect_horiz_commit: Commit points in horiz list to output. + * @ctx: Intersection context. + * + * The main function of the horizontal commit is to output new + * points to the output writer. + * + * This "commit" pass is also where winding numbers are assigned, + * because doing it here provides much greater tolerance for inputs + * which are not in strict SVP order. + * + * Each cluster in the horiz_list contains both segments that are in + * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not, + * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We + * need to deal with both. + **/ +static void +art_svp_intersect_horiz_commit (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + int winding_number = 0; /* initialization just to avoid warning */ + int horiz_wind = 0; + double last_x = 0; /* initialization just to avoid warning */ + +#ifdef VERBOSE + art_dprint ("art_svp_intersect_horiz_commit: y=%g\n", ctx->y); + for (seg = ctx->horiz_first; seg != NULL; seg = seg->horiz_right) + art_dprint (" %lx: %g %+d\n", + (unsigned long)seg, seg->horiz_x, seg->horiz_delta_wind); +#endif + + /* Output points to svp writer. */ + for (seg = ctx->horiz_first; seg != NULL;) + { + /* Find a cluster with common horiz_x, */ + ArtActiveSeg *curs; + double x = seg->horiz_x; + + /* Generate any horizontal segments. */ + if (horiz_wind != 0) + { + ArtSvpWriter *swr = ctx->out; + int seg_id; + + seg_id = swr->add_segment (swr, winding_number, horiz_wind, + last_x, ctx->y); + swr->add_point (swr, seg_id, x, ctx->y); + swr->close_segment (swr, seg_id); + } + + /* Find first active segment in cluster. */ + + for (curs = seg; curs != NULL && curs->horiz_x == x; + curs = curs->horiz_right) + if (!(curs->flags & ART_ACTIVE_FLAGS_DEL)) + break; + + if (curs != NULL && curs->horiz_x == x) + { + /* There exists at least one active segment in this cluster. */ + + /* Find beginning of cluster. */ + for (; curs->left != NULL; curs = curs->left) + if (curs->left->horiz_x != x) + break; + + if (curs->left != NULL) + winding_number = curs->left->wind_left + curs->left->delta_wind; + else + winding_number = 0; + + do + { +#ifdef VERBOSE + art_dprint (" curs %lx: winding_number = %d += %d\n", + (unsigned long)curs, winding_number, curs->delta_wind); +#endif + if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) || + curs->wind_left != winding_number) + { + ArtSvpWriter *swr = ctx->out; + + if (curs->flags & ART_ACTIVE_FLAGS_OUT) + { + swr->add_point (swr, curs->seg_id, + curs->horiz_x, ctx->y); + swr->close_segment (swr, curs->seg_id); + } + + curs->seg_id = swr->add_segment (swr, winding_number, + curs->delta_wind, + x, ctx->y); + curs->flags |= ART_ACTIVE_FLAGS_OUT; + } + curs->wind_left = winding_number; + winding_number += curs->delta_wind; + curs = curs->right; + } + while (curs != NULL && curs->horiz_x == x); + } + + /* Skip past cluster. */ + do + { + ArtActiveSeg *next = seg->horiz_right; + + seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ; + horiz_wind += seg->horiz_delta_wind; + seg->horiz_delta_wind = 0; + if (seg->flags & ART_ACTIVE_FLAGS_DEL) + { + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + ArtSvpWriter *swr = ctx->out; + swr->close_segment (swr, seg->seg_id); + } + art_svp_intersect_active_free (seg); + } + seg = next; + } + while (seg != NULL && seg->horiz_x == x); + + last_x = x; + } + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; +#ifdef SANITYCHECK + art_svp_intersect_sanitycheck_winding (ctx); +#endif +} + +#ifdef VERBOSE +static void +art_svp_intersect_print_active (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + + art_dprint ("Active list (y = %g):\n", ctx->y); + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + art_dprint (" %lx: (%g, %g)-(%g, %g), (a, b, c) = (%g, %g, %g)\n", + (unsigned long)seg, + seg->x[0], seg->y0, seg->x[1], seg->y1, + seg->a, seg->b, seg->c); + } +} +#endif + +#ifdef SANITYCHECK +static void +art_svp_intersect_sanitycheck (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + ArtActiveSeg *last = NULL; + double d; + + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + if (seg->left != last) + { + art_warn ("*** art_svp_intersect_sanitycheck: last=%lx, seg->left=%lx\n", + (unsigned long)last, (unsigned long)seg->left); + } + if (last != NULL) + { + /* pairwise compare with previous seg */ + + /* First the top. */ + if (last->y0 < seg->y0) + { + } + else + { + } + + /* Then the bottom. */ + if (last->y1 < seg->y1) + { + if (!((last->x[1] < + seg->x[(seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) || + last->y1 == seg->y0)) + { + d = last->x[1] * seg->a + last->y1 * seg->b + seg->c; + if (d >= -EPSILON_A) + art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to right (d = %g)\n", + last->x[1], last->y1, (unsigned long) last, + (unsigned long) seg, d); + } + } + else if (last->y1 > seg->y1) + + { + if (!((seg->x[1] > + last->x[last->flags & ART_ACTIVE_FLAGS_BNEG]) || + seg->y1 == last->y0)) + { + d = seg->x[1] * last->a + seg->y1 * last->b + last->c; + if (d <= EPSILON_A) + art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to left (d = %g)\n", + seg->x[1], seg->y1, (unsigned long) seg, + (unsigned long) last, d); + } + } + else + { + if (last->x[1] > seg->x[1]) + art_warn ("*** bottoms (%g, %g) of %lx and (%g, %g) of %lx out of order\n", + last->x[1], last->y1, (unsigned long)last, + seg->x[1], seg->y1, (unsigned long)seg); + } + } + last = seg; + } +} +#endif + +void +art_svp_intersector (const ArtSVP *in, ArtSvpWriter *out) +{ + ArtIntersectCtx *ctx; + ArtPriQ *pq; + ArtPriPoint *first_point; +#ifdef VERBOSE + int count = 0; +#endif + + if (in->n_segs == 0) + return; + + ctx = art_new (ArtIntersectCtx, 1); + ctx->in = in; + ctx->out = out; + pq = art_pri_new (); + ctx->pq = pq; + + ctx->active_head = NULL; + + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; + + ctx->in_curs = 0; + first_point = art_new (ArtPriPoint, 1); + first_point->x = in->segs[0].points[0].x; + first_point->y = in->segs[0].points[0].y; + first_point->user_data = NULL; + ctx->y = first_point->y; + art_pri_insert (pq, first_point); + + while (!art_pri_empty (pq)) + { + ArtPriPoint *pri_point = art_pri_choose (pq); + ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data; + +#ifdef VERBOSE + art_dprint ("\nIntersector step %d\n", count++); + art_svp_intersect_print_active (ctx); + art_dprint ("priq choose (%g, %g) %lx\n", pri_point->x, pri_point->y, + (unsigned long)pri_point->user_data); +#endif +#ifdef SANITYCHECK + art_svp_intersect_sanitycheck(ctx); +#endif + + if (ctx->y != pri_point->y) + { + art_svp_intersect_horiz_commit (ctx); + ctx->y = pri_point->y; + } + + if (seg == NULL) + { + /* Insert new segment from input */ + const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++]; + art_svp_intersect_add_seg (ctx, in_seg); + if (ctx->in_curs < in->n_segs) + { + const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs]; + pri_point->x = next_seg->points[0].x; + pri_point->y = next_seg->points[0].y; + /* user_data is already NULL */ + art_pri_insert (pq, pri_point); + } + else + art_free (pri_point); + } + else + { + int n_stack = seg->n_stack; + + if (n_stack > 1) + { + art_svp_intersect_process_intersection (ctx, seg); + art_free (pri_point); + } + else + { + art_svp_intersect_advance_cursor (ctx, seg, pri_point); + } + } + } + + art_svp_intersect_horiz_commit (ctx); + + art_pri_free (pq); + art_free (ctx); +} + +#endif /* not TEST_PRIQ */ diff --git a/libart_lgpl/art_svp_intersect.h b/libart_lgpl/art_svp_intersect.h new file mode 100644 index 0000000000..c73b3530c5 --- /dev/null +++ b/libart_lgpl/art_svp_intersect.h @@ -0,0 +1,68 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2001 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_INTERSECT_H__ +#define __ART_SVP_INTERSECT_H__ + +/* The funky new SVP intersector. */ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef ART_WIND_RULE_DEFINED +#define ART_WIND_RULE_DEFINED +typedef enum { + ART_WIND_RULE_NONZERO, + ART_WIND_RULE_INTERSECT, + ART_WIND_RULE_ODDEVEN, + ART_WIND_RULE_POSITIVE +} ArtWindRule; +#endif + +typedef struct _ArtSvpWriter ArtSvpWriter; + +struct _ArtSvpWriter { + int (*add_segment) (ArtSvpWriter *self, int wind_left, int delta_wind, + double x, double y); + void (*add_point) (ArtSvpWriter *self, int seg_id, double x, double y); + void (*close_segment) (ArtSvpWriter *self, int seg_id); +}; + +ArtSvpWriter * +art_svp_writer_rewind_new (ArtWindRule rule); + +ArtSVP * +art_svp_writer_rewind_reap (ArtSvpWriter *self); + +#if 0 /* XXX already declared in art_svp.h */ +int +art_svp_seg_compare (const void *s1, const void *s2); +#endif + +void +art_svp_intersector (const ArtSVP *in, ArtSvpWriter *out); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_INTERSECT_H__ */ diff --git a/libart_lgpl/art_svp_ops.c b/libart_lgpl/art_svp_ops.c new file mode 100644 index 0000000000..5bb837c5ac --- /dev/null +++ b/libart_lgpl/art_svp_ops.c @@ -0,0 +1,401 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define noVERBOSE + +/* Vector path set operations, over sorted vpaths. */ + +#include "config.h" +#include "art_svp_ops.h" + +#include "art_misc.h" + +#include "art_svp.h" +#include "art_vpath.h" +#include "art_svp_vpath.h" +#include "art_svp.h" +#ifdef ART_USE_NEW_INTERSECTOR +#include "art_svp_intersect.h" +#else +#include "art_svp_wind.h" +#endif +#include "art_vpath_svp.h" + +/* Merge the segments of the two svp's. The resulting svp will share + segments with args passed in, so be super-careful with the + allocation. */ +/** + * art_svp_merge: Merge the segments of two svp's. + * @svp1: One svp to merge. + * @svp2: The other svp to merge. + * + * Merges the segments of two SVP's into a new one. The resulting + * #ArtSVP data structure will share the segments of the argument + * svp's, so it is probably a good idea to free it shallowly, + * especially if the arguments will be freed with art_svp_free(). + * + * Return value: The merged #ArtSVP. + **/ +static ArtSVP * +art_svp_merge (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtSVP *svp_new; + int ix; + int ix1, ix2; + + svp_new = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (svp1->n_segs + svp2->n_segs - 1) * + sizeof(ArtSVPSeg)); + ix1 = 0; + ix2 = 0; + for (ix = 0; ix < svp1->n_segs + svp2->n_segs; ix++) + { + if (ix1 < svp1->n_segs && + (ix2 == svp2->n_segs || + art_svp_seg_compare (&svp1->segs[ix1], &svp2->segs[ix2]) < 1)) + svp_new->segs[ix] = svp1->segs[ix1++]; + else + svp_new->segs[ix] = svp2->segs[ix2++]; + } + + svp_new->n_segs = ix; + return svp_new; +} + +#ifdef VERBOSE + +#define XOFF 50 +#define YOFF 700 + +static void +print_ps_vpath (ArtVpath *vpath) +{ + int i; + + printf ("gsave %d %d translate 1 -1 scale\n", XOFF, YOFF); + for (i = 0; vpath[i].code != ART_END; i++) + { + switch (vpath[i].code) + { + case ART_MOVETO: + printf ("%g %g moveto\n", vpath[i].x, vpath[i].y); + break; + case ART_LINETO: + printf ("%g %g lineto\n", vpath[i].x, vpath[i].y); + break; + default: + break; + } + } + printf ("stroke grestore showpage\n"); +} + +#define DELT 4 + +static void +print_ps_svp (ArtSVP *vpath) +{ + int i, j; + + printf ("%% begin\n"); + for (i = 0; i < vpath->n_segs; i++) + { + printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0); + for (j = 0; j < vpath->segs[i].n_points; j++) + { + printf ("%g %g %s\n", + XOFF + vpath->segs[i].points[j].x, + YOFF - vpath->segs[i].points[j].y, + j ? "lineto" : "moveto"); + } + printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n", + XOFF + vpath->segs[i].points[0].x - DELT, + YOFF - DELT - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x - DELT, + YOFF - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x + DELT, + YOFF - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x + DELT, + YOFF - DELT - vpath->segs[i].points[0].y); + printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n", + XOFF + vpath->segs[i].points[j - 1].x - DELT, + YOFF + DELT - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x - DELT, + YOFF - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x + DELT, + YOFF - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x + DELT, + YOFF + DELT - vpath->segs[i].points[j - 1].y); + printf ("stroke\n"); + } + + printf ("showpage\n"); +} +#endif + +#ifndef ART_USE_NEW_INTERSECTOR +static ArtSVP * +art_svp_merge_perturbed (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtVpath *vpath1, *vpath2; + ArtVpath *vpath1_p, *vpath2_p; + ArtSVP *svp1_p, *svp2_p; + ArtSVP *svp_new; + + vpath1 = art_vpath_from_svp (svp1); + vpath1_p = art_vpath_perturb (vpath1); + art_free (vpath1); + svp1_p = art_svp_from_vpath (vpath1_p); + art_free (vpath1_p); + + vpath2 = art_vpath_from_svp (svp2); + vpath2_p = art_vpath_perturb (vpath2); + art_free (vpath2); + svp2_p = art_svp_from_vpath (vpath2_p); + art_free (vpath2_p); + + svp_new = art_svp_merge (svp1_p, svp2_p); +#ifdef VERBOSE + print_ps_svp (svp1_p); + print_ps_svp (svp2_p); + print_ps_svp (svp_new); +#endif + art_free (svp1_p); + art_free (svp2_p); + + return svp_new; +} +#endif + +/* Compute the union of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Would be better if we didn't go to unsorted vector path + and back to add the perturbation. + + Precision: The perturbation fuzzes the coordinates slightly. In + cases of butting segments, razor thin long holes may appear. + +*/ +/** + * art_svp_union: Compute the union of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the union of the two argument svp's. Given two svp's with + * winding numbers of 0 and 1 everywhere, the resulting winding number + * will be 1 where either (or both) of the argument svp's has a + * winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The union of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_POSITIVE); +#ifdef VERBOSE + print_ps_svp (svp4); + print_ps_svp (svp_new); +#endif + art_svp_free (svp4); + return svp_new; +#endif +} + +/* Compute the intersection of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Would be better if we didn't go to unsorted vector path + and back to add the perturbation. + + Precision: The perturbation fuzzes the coordinates slightly. In + cases of butting segments, razor thin long isolated segments may + appear. + +*/ + +/** + * art_svp_intersect: Compute the intersection of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the intersection of the two argument svp's. Given two + * svp's with winding numbers of 0 and 1 everywhere, the resulting + * winding number will be 1 where both of the argument svp's has a + * winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The intersection of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_INTERSECT); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_INTERSECT); + art_svp_free (svp4); + return svp_new; +#endif +} + +/* Compute the symmetric difference of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: We could do a lot better by scanning through the svp + representations and culling out any segments that are exactly + identical. It would also be better if we didn't go to unsorted + vector path and back to add the perturbation. + + Precision: Awful. In the case of inputs which are similar (the + common case for canvas display), the entire outline is "hairy." In + addition, the perturbation fuzzes the coordinates slightly. It can + be used as a conservative approximation. + +*/ + +/** + * art_svp_diff: Compute the symmetric difference of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the symmetric of the two argument svp's. Given two svp's + * with winding numbers of 0 and 1 everywhere, the resulting winding + * number will be 1 where either, but not both, of the argument svp's + * has a winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The symmetric difference of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_ODDEVEN); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_ODDEVEN); + art_svp_free (svp4); + return svp_new; +#endif +} + +#ifdef ART_USE_NEW_INTERSECTOR +ArtSVP * +art_svp_minus (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtSVP *svp2_mod; + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + int i; + + svp2_mod = (ArtSVP *) svp2; /* get rid of the const for a while */ + + /* First invert svp2 to "turn it inside out" */ + for (i = 0; i < svp2_mod->n_segs; i++) + svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir; + + svp3 = art_svp_merge (svp1, svp2_mod); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + /* Flip svp2 back to its original state */ + for (i = 0; i < svp2_mod->n_segs; i++) + svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir; + + return svp_new; +} +#endif /* ART_USE_NEW_INTERSECTOR */ diff --git a/libart_lgpl/art_svp_ops.h b/libart_lgpl/art_svp_ops.h new file mode 100644 index 0000000000..1e511f5ed2 --- /dev/null +++ b/libart_lgpl/art_svp_ops.h @@ -0,0 +1,40 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_OPS_H__ +#define __ART_SVP_OPS_H__ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Vector path set operations, over sorted vpaths. */ + +ArtSVP *art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2); +ArtSVP *art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2); +ArtSVP *art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2); +ArtSVP *art_svp_minus (const ArtSVP *svp1, const ArtSVP *svp2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_OPS_H__ */ diff --git a/libart_lgpl/art_svp_point.c b/libart_lgpl/art_svp_point.c new file mode 100644 index 0000000000..4b41e8c402 --- /dev/null +++ b/libart_lgpl/art_svp_point.c @@ -0,0 +1,144 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_svp_point.h" + +#include <math.h> +#include "art_misc.h" + +#include "art_svp.h" + +/* Determine whether a point is inside, or near, an svp. */ + +/* return winding number of point wrt svp */ +/** + * art_svp_point_wind: Determine winding number of a point with respect to svp. + * @svp: The svp. + * @x: The X coordinate of the point. + * @y: The Y coordinate of the point. + * + * Determine the winding number of the point @x, @y with respect to @svp. + * + * Return value: the winding number. + **/ +int +art_svp_point_wind (ArtSVP *svp, double x, double y) +{ + int i, j; + int wind = 0; + + for (i = 0; i < svp->n_segs; i++) + { + ArtSVPSeg *seg = &svp->segs[i]; + + if (seg->bbox.y0 > y) + break; + + if (seg->bbox.y1 > y) + { + if (seg->bbox.x1 < x) + wind += seg->dir ? 1 : -1; + else if (seg->bbox.x0 <= x) + { + double x0, y0, x1, y1, dx, dy; + + for (j = 0; j < seg->n_points - 1; j++) + { + if (seg->points[j + 1].y > y) + break; + } + x0 = seg->points[j].x; + y0 = seg->points[j].y; + x1 = seg->points[j + 1].x; + y1 = seg->points[j + 1].y; + + dx = x1 - x0; + dy = y1 - y0; + if ((x - x0) * dy > (y - y0) * dx) + wind += seg->dir ? 1 : -1; + } + } + } + + return wind; +} + +/** + * art_svp_point_dist: Determine distance between point and svp. + * @svp: The svp. + * @x: The X coordinate of the point. + * @y: The Y coordinate of the point. + * + * Determines the distance of the point @x, @y to the closest edge in + * @svp. A large number is returned if @svp is empty. + * + * Return value: the distance. + **/ +double +art_svp_point_dist (ArtSVP *svp, double x, double y) +{ + int i, j; + double dist_sq; + double best_sq = -1; + + for (i = 0; i < svp->n_segs; i++) + { + ArtSVPSeg *seg = &svp->segs[i]; + for (j = 0; j < seg->n_points - 1; j++) + { + double x0 = seg->points[j].x; + double y0 = seg->points[j].y; + double x1 = seg->points[j + 1].x; + double y1 = seg->points[j + 1].y; + + double dx = x1 - x0; + double dy = y1 - y0; + + double dxx0 = x - x0; + double dyy0 = y - y0; + + double dot = dxx0 * dx + dyy0 * dy; + + if (dot < 0) + dist_sq = dxx0 * dxx0 + dyy0 * dyy0; + else + { + double rr = dx * dx + dy * dy; + + if (dot > rr) + dist_sq = (x - x1) * (x - x1) + (y - y1) * (y - y1); + else + { + double perp = (y - y0) * dx - (x - x0) * dy; + + dist_sq = perp * perp / rr; + } + } + if (best_sq < 0 || dist_sq < best_sq) + best_sq = dist_sq; + } + } + + if (best_sq >= 0) + return sqrt (best_sq); + else + return 1e12; +} + diff --git a/libart_lgpl/art_svp_point.h b/libart_lgpl/art_svp_point.h new file mode 100644 index 0000000000..c150d3e579 --- /dev/null +++ b/libart_lgpl/art_svp_point.h @@ -0,0 +1,45 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_POINT_H__ +#define __ART_SVP_POINT_H__ + +/* Determine whether a point is inside, or near, an svp. */ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int +art_svp_point_wind (ArtSVP *svp, double x, double y); + +double +art_svp_point_dist (ArtSVP *svp, double x, double y); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_H__ */ + + + + diff --git a/libart_lgpl/art_svp_render_aa.c b/libart_lgpl/art_svp_render_aa.c new file mode 100644 index 0000000000..d696a51f68 --- /dev/null +++ b/libart_lgpl/art_svp_render_aa.c @@ -0,0 +1,463 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* The spiffy antialiased renderer for sorted vector paths. */ + +#include "config.h" +#include "art_svp_render_aa.h" + +#include <math.h> +#include <string.h> /* for memmove */ +#include "art_misc.h" + +#include "art_rect.h" +#include "art_svp.h" + +#include <stdio.h> + +typedef double artfloat; + +struct _ArtSVPRenderAAIter { + const ArtSVP *svp; + int x0, x1; + int y; + int seg_ix; + + int *active_segs; + int n_active_segs; + int *cursor; + artfloat *seg_x; + artfloat *seg_dx; + + ArtSVPRenderAAStep *steps; +}; + +static void +art_svp_render_insert_active (int i, int *active_segs, int n_active_segs, + artfloat *seg_x, artfloat *seg_dx) +{ + int j; + artfloat x; + int tmp1, tmp2; + + /* this is a cheap hack to get ^'s sorted correctly */ + x = seg_x[i] + 0.001 * seg_dx[i]; + for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++); + + tmp1 = i; + while (j < n_active_segs) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + j++; + } + active_segs[j] = tmp1; +} + +static void +art_svp_render_delete_active (int *active_segs, int j, int n_active_segs) +{ + int k; + + for (k = j; k < n_active_segs; k++) + active_segs[k] = active_segs[k + 1]; +} + +#define EPSILON 1e-6 + +/* Render the sorted vector path in the given rectangle, antialiased. + + This interface uses a callback for the actual pixel rendering. The + callback is called y1 - y0 times (once for each scan line). The y + coordinate is given as an argument for convenience (it could be + stored in the callback's private data and incremented on each + call). + + The rendered polygon is represented in a semi-runlength format: a + start value and a sequence of "steps". Each step has an x + coordinate and a value delta. The resulting value at position x is + equal to the sum of the start value and all step delta values for + which the step x coordinate is less than or equal to x. An + efficient algorithm will traverse the steps left to right, keeping + a running sum. + + All x coordinates in the steps are guaranteed to be x0 <= x < x1. + (This guarantee is a change from the gfonted vpaar renderer, and is + designed to simplify the callback). + + There is now a further guarantee that no two steps will have the + same x value. This may allow for further speedup and simplification + of renderers. + + The value 0x8000 represents 0% coverage by the polygon, while + 0xff8000 represents 100% coverage. This format is designed so that + >> 16 results in a standard 0x00..0xff value range, with nice + rounding. + + Status of this routine: + + Basic correctness: OK + + Numerical stability: pretty good, although probably not + bulletproof. + + Speed: Needs more aggressive culling of bounding boxes. Can + probably speed up the [x0,x1) clipping of step values. Can do more + of the step calculation in fixed point. + + Precision: No known problems, although it should be tested + thoroughly, especially for symmetry. + +*/ + +ArtSVPRenderAAIter * +art_svp_render_aa_iter (const ArtSVP *svp, + int x0, int y0, int x1, int y1) +{ + ArtSVPRenderAAIter *iter = art_new (ArtSVPRenderAAIter, 1); + + iter->svp = svp; + iter->y = y0; + iter->x0 = x0; + iter->x1 = x1; + iter->seg_ix = 0; + + iter->active_segs = art_new (int, svp->n_segs); + iter->cursor = art_new (int, svp->n_segs); + iter->seg_x = art_new (artfloat, svp->n_segs); + iter->seg_dx = art_new (artfloat, svp->n_segs); + iter->steps = art_new (ArtSVPRenderAAStep, x1 - x0); + iter->n_active_segs = 0; + + return iter; +} + +#define ADD_STEP(xpos, xdelta) \ + /* stereotype code fragment for adding a step */ \ + if (n_steps == 0 || steps[n_steps - 1].x < xpos) \ + { \ + sx = n_steps; \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + else \ + { \ + for (sx = n_steps; sx > 0; sx--) \ + { \ + if (steps[sx - 1].x == xpos) \ + { \ + steps[sx - 1].delta += xdelta; \ + sx = n_steps; \ + break; \ + } \ + else if (steps[sx - 1].x < xpos) \ + { \ + break; \ + } \ + } \ + if (sx < n_steps) \ + { \ + memmove (&steps[sx + 1], &steps[sx], \ + (n_steps - sx) * sizeof(steps[0])); \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + } + +void +art_svp_render_aa_iter_step (ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps) +{ + const ArtSVP *svp = iter->svp; + int *active_segs = iter->active_segs; + int n_active_segs = iter->n_active_segs; + int *cursor = iter->cursor; + artfloat *seg_x = iter->seg_x; + artfloat *seg_dx = iter->seg_dx; + int i = iter->seg_ix; + int j; + int x0 = iter->x0; + int x1 = iter->x1; + int y = iter->y; + int seg_index; + + int x; + ArtSVPRenderAAStep *steps = iter->steps; + int n_steps; + artfloat y_top, y_bot; + artfloat x_top, x_bot; + artfloat x_min, x_max; + int ix_min, ix_max; + artfloat delta; /* delta should be int too? */ + int last, this; + int xdelta; + artfloat rslope, drslope; + int start; + const ArtSVPSeg *seg; + int curs; + artfloat dy; + + int sx; + + /* insert new active segments */ + for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) + { + if (svp->segs[i].bbox.y1 > y && + svp->segs[i].bbox.x0 < x1) + { + seg = &svp->segs[i]; + /* move cursor to topmost vector which overlaps [y,y+1) */ + for (curs = 0; seg->points[curs + 1].y < y; curs++); + cursor[i] = curs; + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs (dy) >= EPSILON) + seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / + dy; + else + seg_dx[i] = 1e12; + seg_x[i] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[i]; + art_svp_render_insert_active (i, active_segs, n_active_segs++, + seg_x, seg_dx); + } + } + + n_steps = 0; + + /* render the runlengths, advancing and deleting as we go */ + start = 0x8000; + + for (j = 0; j < n_active_segs; j++) + { + seg_index = active_segs[j]; + seg = &svp->segs[seg_index]; + curs = cursor[seg_index]; + while (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) + { + y_top = y; + if (y_top < seg->points[curs].y) + y_top = seg->points[curs].y; + y_bot = y + 1; + if (y_bot > seg->points[curs + 1].y) + y_bot = seg->points[curs + 1].y; + if (y_top != y_bot) { + delta = (seg->dir ? 16711680.0 : -16711680.0) * + (y_bot - y_top); + x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index]; + x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index]; + if (x_top < x_bot) + { + x_min = x_top; + x_max = x_bot; + } + else + { + x_min = x_bot; + x_max = x_top; + } + ix_min = floor (x_min); + ix_max = floor (x_max); + if (ix_min >= x1) + { + /* skip; it starts to the right of the render region */ + } + else if (ix_max < x0) + /* it ends to the left of the render region */ + start += delta; + else if (ix_min == ix_max) + { + /* case 1, antialias a single pixel */ + xdelta = (ix_min + 1 - (x_min + x_max) * 0.5) * delta; + + ADD_STEP(ix_min, xdelta) + + if (ix_min + 1 < x1) + { + xdelta = delta - xdelta; + + ADD_STEP(ix_min + 1, xdelta) + } + } + else + { + /* case 2, antialias a run */ + rslope = 1.0 / fabs (seg_dx[seg_index]); + drslope = delta * rslope; + last = + drslope * 0.5 * + (ix_min + 1 - x_min) * (ix_min + 1 - x_min); + xdelta = last; + if (ix_min >= x0) + { + ADD_STEP(ix_min, xdelta) + + x = ix_min + 1; + } + else + { + start += last; + x = x0; + } + if (ix_max > x1) + ix_max = x1; + for (; x < ix_max; x++) + { + this = (seg->dir ? 16711680.0 : -16711680.0) * rslope * + (x + 0.5 - x_min); + xdelta = this - last; + last = this; + + ADD_STEP(x, xdelta) + } + if (x < x1) + { + this = + delta * (1 - 0.5 * + (x_max - ix_max) * (x_max - ix_max) * + rslope); + xdelta = this - last; + last = this; + + ADD_STEP(x, xdelta) + + if (x + 1 < x1) + { + xdelta = delta - last; + + ADD_STEP(x + 1, xdelta) + } + } + } + } + curs++; + if (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) + { + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs (dy) >= EPSILON) + seg_dx[seg_index] = (seg->points[curs + 1].x - + seg->points[curs].x) / dy; + else + seg_dx[seg_index] = 1e12; + seg_x[seg_index] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[seg_index]; + } + /* break here, instead of duplicating predicate in while? */ + } + if (seg->points[curs].y >= y + 1) + { + curs--; + cursor[seg_index] = curs; + seg_x[seg_index] += seg_dx[seg_index]; + } + else + { + art_svp_render_delete_active (active_segs, j--, + --n_active_segs); + } + } + + *p_start = start; + *p_steps = steps; + *p_n_steps = n_steps; + + iter->seg_ix = i; + iter->n_active_segs = n_active_segs; + iter->y++; +} + +void +art_svp_render_aa_iter_done (ArtSVPRenderAAIter *iter) +{ + art_free (iter->steps); + + art_free (iter->seg_dx); + art_free (iter->seg_x); + art_free (iter->cursor); + art_free (iter->active_segs); + art_free (iter); +} + +/** + * art_svp_render_aa: Render SVP antialiased. + * @svp: The #ArtSVP to render. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @callback: The callback which actually paints the pixels. + * @callback_data: Private data for @callback. + * + * Renders the sorted vector path in the given rectangle, antialiased. + * + * This interface uses a callback for the actual pixel rendering. The + * callback is called @y1 - @y0 times (once for each scan line). The y + * coordinate is given as an argument for convenience (it could be + * stored in the callback's private data and incremented on each + * call). + * + * The rendered polygon is represented in a semi-runlength format: a + * start value and a sequence of "steps". Each step has an x + * coordinate and a value delta. The resulting value at position x is + * equal to the sum of the start value and all step delta values for + * which the step x coordinate is less than or equal to x. An + * efficient algorithm will traverse the steps left to right, keeping + * a running sum. + * + * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1. + * (This guarantee is a change from the gfonted vpaar renderer from + * which this routine is derived, and is designed to simplify the + * callback). + * + * The value 0x8000 represents 0% coverage by the polygon, while + * 0xff8000 represents 100% coverage. This format is designed so that + * >> 16 results in a standard 0x00..0xff value range, with nice + * rounding. + * + **/ +void +art_svp_render_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback) (void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data) +{ + ArtSVPRenderAAIter *iter; + int y; + int start; + ArtSVPRenderAAStep *steps; + int n_steps; + + iter = art_svp_render_aa_iter (svp, x0, y0, x1, y1); + + + for (y = y0; y < y1; y++) + { + art_svp_render_aa_iter_step (iter, &start, &steps, &n_steps); + (*callback) (callback_data, y, start, steps, n_steps); + } + + art_svp_render_aa_iter_done (iter); +} diff --git a/libart_lgpl/art_svp_render_aa.h b/libart_lgpl/art_svp_render_aa.h new file mode 100644 index 0000000000..c0c36874dc --- /dev/null +++ b/libart_lgpl/art_svp_render_aa.h @@ -0,0 +1,63 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_RENDER_AA_H__ +#define __ART_SVP_RENDER_AA_H__ + +/* The spiffy antialiased renderer for sorted vector paths. */ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtSVPRenderAAStep ArtSVPRenderAAStep; +typedef struct _ArtSVPRenderAAIter ArtSVPRenderAAIter; + +struct _ArtSVPRenderAAStep { + int x; + int delta; /* stored with 16 fractional bits */ +}; + +ArtSVPRenderAAIter * +art_svp_render_aa_iter (const ArtSVP *svp, + int x0, int y0, int x1, int y1); + +void +art_svp_render_aa_iter_step (ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps); + +void +art_svp_render_aa_iter_done (ArtSVPRenderAAIter *iter); + +void +art_svp_render_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback) (void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_RENDER_AA_H__ */ diff --git a/libart_lgpl/art_svp_vpath.c b/libart_lgpl/art_svp_vpath.c new file mode 100644 index 0000000000..196711a400 --- /dev/null +++ b/libart_lgpl/art_svp_vpath.c @@ -0,0 +1,215 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Sort vector paths into sorted vector paths */ + +#include "config.h" +#include "art_svp_vpath.h" + +#include <stdlib.h> +#include <math.h> + +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" + + +/* reverse a list of points in place */ +static void +reverse_points (ArtPoint *points, int n_points) +{ + int i; + ArtPoint tmp_p; + + for (i = 0; i < (n_points >> 1); i++) + { + tmp_p = points[i]; + points[i] = points[n_points - (i + 1)]; + points[n_points - (i + 1)] = tmp_p; + } +} + +/** + * art_svp_from_vpath: Convert a vpath to a sorted vector path. + * @vpath: #ArtVPath to convert. + * + * Converts a vector path into sorted vector path form. The svp form is + * more efficient for rendering and other vector operations. + * + * Basically, the implementation is to traverse the vector path, + * generating a new segment for each "run" of points in the vector + * path with monotonically increasing Y values. All the resulting + * values are then sorted. + * + * Note: I'm not sure that the sorting rule is correct with respect + * to numerical stability issues. + * + * Return value: Resulting sorted vector path. + **/ +ArtSVP * +art_svp_from_vpath (ArtVpath *vpath) +{ + int n_segs, n_segs_max; + ArtSVP *svp; + int dir; + int new_dir; + int i; + ArtPoint *points; + int n_points, n_points_max; + double x, y; + double x_min, x_max; + + n_segs = 0; + n_segs_max = 16; + svp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + + dir = 0; + n_points = 0; + n_points_max = 0; + points = NULL; + i = 0; + + x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant, + but it makes gcc -Wall -ansi -pedantic happier */ + x_min = x_max = 0; /* same */ + + while (vpath[i].code != ART_END) { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) + { + if (points != NULL && n_points >= 2) + { + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + points = NULL; + } + + if (points == NULL) + { + n_points_max = 4; + points = art_new (ArtPoint, n_points_max); + } + + n_points = 1; + points[0].x = x = vpath[i].x; + points[0].y = y = vpath[i].y; + x_min = x; + x_max = x; + dir = 0; + } + else /* must be LINETO */ + { + new_dir = (vpath[i].y > y || + (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1; + if (dir && dir != new_dir) + { + /* new segment */ + x = points[n_points - 1].x; + y = points[n_points - 1].y; + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + + n_points = 1; + n_points_max = 4; + points = art_new (ArtPoint, n_points_max); + points[0].x = x; + points[0].y = y; + x_min = x; + x_max = x; + } + + if (points != NULL) + { + if (n_points == n_points_max) + art_expand (points, ArtPoint, n_points_max); + points[n_points].x = x = vpath[i].x; + points[n_points].y = y = vpath[i].y; + if (x < x_min) x_min = x; + else if (x > x_max) x_max = x; + n_points++; + } + dir = new_dir; + } + i++; + } + + if (points != NULL) + { + if (n_points >= 2) + { + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + } + else + art_free (points); + } + + svp->n_segs = n_segs; + + qsort (&svp->segs, n_segs, sizeof (ArtSVPSeg), art_svp_seg_compare); + + return svp; +} + diff --git a/libart_lgpl/art_svp_vpath.h b/libart_lgpl/art_svp_vpath.h new file mode 100644 index 0000000000..ae75adcc84 --- /dev/null +++ b/libart_lgpl/art_svp_vpath.h @@ -0,0 +1,39 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_VPATH_H__ +#define __ART_SVP_VPATH_H__ + +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath.h> + +/* Sort vector paths into sorted vector paths. */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtSVP * +art_svp_from_vpath (ArtVpath *vpath); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_VPATH_H__ */ diff --git a/libart_lgpl/art_svp_vpath_stroke.c b/libart_lgpl/art_svp_vpath_stroke.c new file mode 100644 index 0000000000..8d532f9743 --- /dev/null +++ b/libart_lgpl/art_svp_vpath_stroke.c @@ -0,0 +1,739 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "config.h" +#include "art_svp_vpath_stroke.h" + +#include <stdlib.h> +#include <math.h> + +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" +#ifdef ART_USE_NEW_INTERSECTOR +#include "art_svp_intersect.h" +#else +#include "art_svp_wind.h" +#endif +#include "art_svp_vpath.h" + +#define EPSILON 1e-6 +#define EPSILON_2 1e-12 + +#define yes_OPTIMIZE_INNER + +/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1, + yc + y1), centered at (xc, yc), and with given radius. Both x0^2 + + y0^2 and x1^2 + y1^2 should be equal to radius^2. + + A positive value of radius means curve to the left, negative means + curve to the right. +*/ +static void +art_svp_vpath_stroke_arc (ArtVpath **p_vpath, int *pn, int *pn_max, + double xc, double yc, + double x0, double y0, + double x1, double y1, + double radius, + double flatness) +{ + double theta; + double th_0, th_1; + int n_pts; + int i; + double aradius; + + aradius = fabs (radius); + theta = 2 * M_SQRT2 * sqrt (flatness / aradius); + th_0 = atan2 (y0, x0); + th_1 = atan2 (y1, x1); + if (radius > 0) + { + /* curve to the left */ + if (th_0 < th_1) th_0 += M_PI * 2; + n_pts = ceil ((th_0 - th_1) / theta); + } + else + { + /* curve to the right */ + if (th_1 < th_0) th_1 += M_PI * 2; + n_pts = ceil ((th_1 - th_0) / theta); + } +#ifdef VERBOSE + printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta); +#endif + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + x0, yc + y0); + for (i = 1; i < n_pts; i++) + { + theta = th_0 + (th_1 - th_0) * i / n_pts; + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + cos (theta) * aradius, + yc + sin (theta) * aradius); +#ifdef VERBOSE + printf ("mid %f %f\n", cos (theta) * radius, sin (theta) * radius); +#endif + } + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + x1, yc + y1); +#ifdef VERBOSE + printf ("end %f %f\n", x1, y1); +#endif +} + +/* Assume that forw and rev are at point i0. Bring them to i1, + joining with the vector i1 - i2. + + This used to be true, but isn't now that the stroke_raw code is + filtering out (near)zero length vectors: {It so happens that all + invocations of this function maintain the precondition i1 = i0 + 1, + so we could decrease the number of arguments by one. We haven't + done that here, though.} + + forw is to the line's right and rev is to its left. + + Precondition: no zero-length vectors, otherwise a divide by + zero will happen. */ +static void +render_seg (ArtVpath **p_forw, int *pn_forw, int *pn_forw_max, + ArtVpath **p_rev, int *pn_rev, int *pn_rev_max, + ArtVpath *vpath, int i0, int i1, int i2, + ArtPathStrokeJoinType join, + double line_width, double miter_limit, double flatness) +{ + double dx0, dy0; + double dx1, dy1; + double dlx0, dly0; + double dlx1, dly1; + double dmx, dmy; + double dmr2; + double scale; + double cross; + +#ifdef VERBOSE + printf ("join style = %d\n", join); +#endif + + /* The vectors of the lines from i0 to i1 and i1 to i2. */ + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + dx1 = vpath[i2].x - vpath[i1].x; + dy1 = vpath[i2].y - vpath[i1].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx1 * dx1 + dy1 * dy1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + +#ifdef VERBOSE + printf ("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n", + vpath[i0].x, vpath[i0].y, + vpath[i1].x, vpath[i1].y, + vpath[i2].x, vpath[i2].y); + + printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n", + dx0, dy0, dlx0, dly0); + + printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n", + dx1, dy1, dlx1, dly1); +#endif + + /* now, forw's last point is expected to be colinear along d[xy]0 + to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */ + + /* positive for positive area (i.e. left turn) */ + cross = dx1 * dy0 - dx0 * dy1; + + dmx = (dlx0 + dlx1) * 0.5; + dmy = (dly0 + dly1) * 0.5; + dmr2 = dmx * dmx + dmy * dmy; + + if (join == ART_PATH_STROKE_JOIN_MITER && + dmr2 * miter_limit * miter_limit < line_width * line_width) + join = ART_PATH_STROKE_JOIN_BEVEL; + + /* the case when dmr2 is zero or very small bothers me + (i.e. near a 180 degree angle) + ALEX: So, we avoid the optimization when dmr2 is very small. This should + be safe since dmx/y is only used in optimization and in MITER case, and MITER + should be converted to BEVEL when dmr2 is very small. */ + if (dmr2 > EPSILON_2) + { + scale = line_width * line_width / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (cross * cross < EPSILON_2 && dx0 * dx1 + dy0 * dy1 >= 0) + { + /* going straight */ +#ifdef VERBOSE + printf ("%% render_seg: straight\n"); +#endif + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + } + else if (cross > 0) + { + /* left turn, forw is outside and rev is inside */ + +#ifdef VERBOSE + printf ("%% render_seg: left\n"); +#endif + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 + dm[xy] is inside i0-i1 rectangle */ + (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 && + /* and that i1 + dm[xy] is inside i1-i2 rectangle */ + ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */ + (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 && + /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0) +#endif + ) + { + /* can safely add single intersection point */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } + else + { + /* need to loop-de-loop the inside */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) + { + /* bevel */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + else if (join == ART_PATH_STROKE_JOIN_MITER) + { + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } + else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc (p_forw, pn_forw, pn_forw_max, + vpath[i1].x, vpath[i1].y, + -dlx0, -dly0, + -dlx1, -dly1, + line_width, + flatness); + } + else + { + /* right turn, rev is outside and forw is inside */ +#ifdef VERBOSE + printf ("%% render_seg: right\n"); +#endif + + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 - dm[xy] is inside i0-i1 rectangle */ + (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 && + /* and that i1 - dm[xy] is inside i1-i2 rectangle */ + ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */ + (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 && + /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0) +#endif + ) + { + /* can safely add single intersection point */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } + else + { + /* need to loop-de-loop the inside */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) + { + /* bevel */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + else if (join == ART_PATH_STROKE_JOIN_MITER) + { + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } + else if (join == ART_PATH_STROKE_JOIN_ROUND) + art_svp_vpath_stroke_arc (p_rev, pn_rev, pn_rev_max, + vpath[i1].x, vpath[i1].y, + dlx0, dly0, + dlx1, dly1, + -line_width, + flatness); + + } +} + +/* caps i1, under the assumption of a vector from i0 */ +static void +render_cap (ArtVpath **p_result, int *pn_result, int *pn_result_max, + ArtVpath *vpath, int i0, int i1, + ArtPathStrokeCapType cap, double line_width, double flatness) +{ + double dx0, dy0; + double dlx0, dly0; + double scale; + int n_pts; + int i; + + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + +#ifdef VERBOSE + printf ("cap style = %d\n", cap); +#endif + + switch (cap) + { + case ART_PATH_STROKE_CAP_BUTT: + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_ROUND: + n_pts = ceil (M_PI / (2.0 * M_SQRT2 * sqrt (flatness / line_width))); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + for (i = 1; i < n_pts; i++) + { + double theta, c_th, s_th; + + theta = M_PI * i / n_pts; + c_th = cos (theta); + s_th = sin (theta); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 * c_th - dly0 * s_th, + vpath[i1].y - dly0 * c_th + dlx0 * s_th); + } + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_SQUARE: + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 - dly0, + vpath[i1].y - dly0 + dlx0); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x + dlx0 - dly0, + vpath[i1].y + dly0 + dlx0); + break; + } +} + +/** + * art_svp_from_vpath_raw: Stroke a vector path, raw version + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Exactly the same as art_svp_vpath_stroke(), except that the resulting + * stroke outline may self-intersect and have regions of winding number + * greater than 1. + * + * Return value: Resulting raw stroked outline in svp format. + **/ +ArtVpath * +art_svp_vpath_stroke_raw (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) +{ + int begin_idx, end_idx; + int i; + ArtVpath *forw, *rev; + int n_forw, n_rev; + int n_forw_max, n_rev_max; + ArtVpath *result; + int n_result, n_result_max; + double half_lw = 0.5 * line_width; + int closed; + int last, this, next, second; + double dx, dy; + + n_forw_max = 16; + forw = art_new (ArtVpath, n_forw_max); + + n_rev_max = 16; + rev = art_new (ArtVpath, n_rev_max); + + n_result = 0; + n_result_max = 16; + result = art_new (ArtVpath, n_result_max); + + for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx) + { + n_forw = 0; + n_rev = 0; + + closed = (vpath[begin_idx].code == ART_MOVETO); + + /* we don't know what the first point joins with until we get to the + last point and see if it's closed. So we start with the second + line in the path. + + Note: this is not strictly true (we now know it's closed from + the opening pathcode), but why fix code that isn't broken? + */ + + this = begin_idx; + /* skip over identical points at the beginning of the subpath */ + for (i = this + 1; vpath[i].code == ART_LINETO; i++) + { + dx = vpath[i].x - vpath[this].x; + dy = vpath[i].y - vpath[this].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + second = next; + + /* invariant: this doesn't coincide with next */ + while (vpath[next].code == ART_LINETO) + { + last = this; + this = next; + /* skip over identical points after the beginning of the subpath */ + for (i = this + 1; vpath[i].code == ART_LINETO; i++) + { + dx = vpath[i].x - vpath[this].x; + dy = vpath[i].y - vpath[this].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + if (vpath[next].code != ART_LINETO) + { + /* reached end of path */ + /* make "closed" detection conform to PostScript + semantics (i.e. explicit closepath code rather than + just the fact that end of the path is the beginning) */ + if (closed && + vpath[this].x == vpath[begin_idx].x && + vpath[this].y == vpath[begin_idx].y) + { + int j; + + /* path is closed, render join to beginning */ + render_seg (&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this, second, + join, half_lw, miter_limit, flatness); + +#ifdef VERBOSE + printf ("%% forw %d, rev %d\n", n_forw, n_rev); +#endif + /* do forward path */ + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, forw[n_forw - 1].x, + forw[n_forw - 1].y); + for (j = 0; j < n_forw; j++) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + + /* do reverse path, reversed */ + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, rev[0].x, + rev[0].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + } + else + { + /* path is open */ + int j; + + /* add to forw rather than result to ensure that + forw has at least one point. */ + render_cap (&forw, &n_forw, &n_forw_max, + vpath, last, this, + cap, half_lw, flatness); + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, forw[0].x, + forw[0].y); + for (j = 1; j < n_forw; j++) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + render_cap (&result, &n_result, &n_result_max, + vpath, second, begin_idx, + cap, half_lw, flatness); + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[0].x, + forw[0].y); + } + } + else + render_seg (&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this, next, + join, half_lw, miter_limit, flatness); + } + end_idx = next; + } + + art_free (forw); + art_free (rev); +#ifdef VERBOSE + printf ("%% n_result = %d\n", n_result); +#endif + art_vpath_add_point (&result, &n_result, &n_result_max, ART_END, 0, 0); + return result; +} + +#define noVERBOSE + +#ifdef VERBOSE + +#define XOFF 50 +#define YOFF 700 + +static void +print_ps_vpath (ArtVpath *vpath) +{ + int i; + + for (i = 0; vpath[i].code != ART_END; i++) + { + switch (vpath[i].code) + { + case ART_MOVETO: + printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + case ART_LINETO: + printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + default: + break; + } + } + printf ("stroke showpage\n"); +} + +static void +print_ps_svp (ArtSVP *vpath) +{ + int i, j; + + printf ("%% begin\n"); + for (i = 0; i < vpath->n_segs; i++) + { + printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0); + for (j = 0; j < vpath->segs[i].n_points; j++) + { + printf ("%g %g %s\n", + XOFF + vpath->segs[i].points[j].x, + YOFF - vpath->segs[i].points[j].y, + j ? "lineto" : "moveto"); + } + printf ("stroke\n"); + } + + printf ("showpage\n"); +} +#endif + +/* Render a vector path into a stroked outline. + + Status of this routine: + + Basic correctness: Only miter and bevel line joins are implemented, + and only butt line caps. Otherwise, seems to be fine. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Should be pretty good. + + Precision: The perturbation fuzzes the coordinates slightly, + but not enough to be visible. */ +/** + * art_svp_vpath_stroke: Stroke a vector path. + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Computes an svp representing the stroked outline of @vpath. The + * width of the stroked line is @line_width. + * + * Lines are joined according to the @join rule. Possible values are + * ART_PATH_STROKE_JOIN_MITER (for mitered joins), + * ART_PATH_STROKE_JOIN_ROUND (for round joins), and + * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join + * is converted to a bevelled join if the miter would extend to a + * distance of more than @miter_limit * @line_width from the actual + * join point. + * + * If there are open subpaths, the ends of these subpaths are capped + * according to the @cap rule. Possible values are + * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end + * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at + * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap, + * extending half @line_width past the end point). + * + * The @flatness parameter controls the accuracy of the rendering. It + * is most important for determining the number of points to use to + * approximate circular arcs for round lines and joins. In general, the + * resulting vector path will be within @flatness pixels of the "ideal" + * path containing actual circular arcs. I reserve the right to use + * the @flatness parameter to convert bevelled joins to miters for very + * small turn angles, as this would reduce the number of points in the + * resulting outline path. + * + * The resulting path is "clean" with respect to self-intersections, i.e. + * the winding number is 0 or 1 at each point. + * + * Return value: Resulting stroked outline in svp format. + **/ +ArtSVP * +art_svp_vpath_stroke (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtVpath *vpath_stroke; + ArtSVP *svp, *svp2; + ArtSvpWriter *swr; + + vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap, + line_width, miter_limit, flatness); +#ifdef VERBOSE + print_ps_vpath (vpath_stroke); +#endif + svp = art_svp_from_vpath (vpath_stroke); +#ifdef VERBOSE + print_ps_svp (svp); +#endif + art_free (vpath_stroke); + + swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO); + art_svp_intersector (svp, swr); + + svp2 = art_svp_writer_rewind_reap (swr); +#ifdef VERBOSE + print_ps_svp (svp2); +#endif + art_svp_free (svp); + return svp2; +#else + ArtVpath *vpath_stroke, *vpath2; + ArtSVP *svp, *svp2, *svp3; + + vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap, + line_width, miter_limit, flatness); +#ifdef VERBOSE + print_ps_vpath (vpath_stroke); +#endif + vpath2 = art_vpath_perturb (vpath_stroke); +#ifdef VERBOSE + print_ps_vpath (vpath2); +#endif + art_free (vpath_stroke); + svp = art_svp_from_vpath (vpath2); +#ifdef VERBOSE + print_ps_svp (svp); +#endif + art_free (vpath2); + svp2 = art_svp_uncross (svp); +#ifdef VERBOSE + print_ps_svp (svp2); +#endif + art_svp_free (svp); + svp3 = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_NONZERO); +#ifdef VERBOSE + print_ps_svp (svp3); +#endif + art_svp_free (svp2); + + return svp3; +#endif +} diff --git a/libart_lgpl/art_svp_vpath_stroke.h b/libart_lgpl/art_svp_vpath_stroke.h new file mode 100644 index 0000000000..c189a5bb2b --- /dev/null +++ b/libart_lgpl/art_svp_vpath_stroke.h @@ -0,0 +1,65 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_VPATH_STROKE_H__ +#define __ART_SVP_VPATH_STROKE_H__ + +/* Sort vector paths into sorted vector paths. */ + +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_JOIN_ROUND, + ART_PATH_STROKE_JOIN_BEVEL +} ArtPathStrokeJoinType; + +typedef enum { + ART_PATH_STROKE_CAP_BUTT, + ART_PATH_STROKE_CAP_ROUND, + ART_PATH_STROKE_CAP_SQUARE +} ArtPathStrokeCapType; + +ArtSVP * +art_svp_vpath_stroke (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +/* This version may have winding numbers exceeding 1. */ +ArtVpath * +art_svp_vpath_stroke_raw (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_VPATH_STROKE_H__ */ diff --git a/libart_lgpl/art_svp_wind.c b/libart_lgpl/art_svp_wind.c new file mode 100644 index 0000000000..a12b1c7763 --- /dev/null +++ b/libart_lgpl/art_svp_wind.c @@ -0,0 +1,1545 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Primitive intersection and winding number operations on sorted + vector paths. + + These routines are internal to libart, used to construct operations + like intersection, union, and difference. */ + +#include "config.h" +#include "art_svp_wind.h" + +#include <stdio.h> /* for printf of debugging info */ +#include <string.h> /* for memcpy */ +#include <math.h> +#include "art_misc.h" + +#include "art_rect.h" +#include "art_svp.h" + +#define noVERBOSE + +#define PT_EQ(p1,p2) ((p1).x == (p2).x && (p1).y == (p2).y) + +#define PT_CLOSE(p1,p2) (fabs ((p1).x - (p2).x) < 1e-6 && fabs ((p1).y - (p2).y) < 1e-6) + +/* return nonzero and set *p to the intersection point if the lines + z0-z1 and z2-z3 intersect each other. */ +static int +intersect_lines (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3, + ArtPoint *p) +{ + double a01, b01, c01; + double a23, b23, c23; + double d0, d1, d2, d3; + double det; + + /* if the vectors share an endpoint, they don't intersect */ + if (PT_EQ (z0, z2) || PT_EQ (z0, z3) || PT_EQ (z1, z2) || PT_EQ (z1, z3)) + return 0; + +#if 0 + if (PT_CLOSE (z0, z2) || PT_CLOSE (z0, z3) || PT_CLOSE (z1, z2) || PT_CLOSE (z1, z3)) + return 0; +#endif + + /* find line equations ax + by + c = 0 */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = (z1.x * z0.y - z1.y * z0.x) */ + + d2 = a01 * z2.x + b01 * z2.y + c01; + d3 = a01 * z3.x + b01 * z3.y + c01; + if ((d2 > 0) == (d3 > 0)) + return 0; + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + d0 = a23 * z0.x + b23 * z0.y + c23; + d1 = a23 * z1.x + b23 * z1.y + c23; + if ((d0 > 0) == (d1 > 0)) + return 0; + + /* now we definitely know that the lines intersect */ + /* solve the two linear equations ax + by + c = 0 */ + det = 1.0 / (a01 * b23 - a23 * b01); + p->x = det * (c23 * b01 - c01 * b23); + p->y = det * (c01 * a23 - c23 * a01); + + return 1; +} + +#define EPSILON 1e-6 + +static double +trap_epsilon (double v) +{ + const double epsilon = EPSILON; + + if (v < epsilon && v > -epsilon) return 0; + else return v; +} + +/* Determine the order of line segments z0-z1 and z2-z3. + Return +1 if z2-z3 lies entirely to the right of z0-z1, + -1 if entirely to the left, + or 0 if overlap. + + The case analysis in this function is quite ugly. The fact that it's + almost 200 lines long is ridiculous. + + Ok, so here's the plan to cut it down: + + First, do a bounding line comparison on the x coordinates. This is pretty + much the common case, and should go quickly. It also takes care of the + case where both lines are horizontal. + + Then, do d0 and d1 computation, but only if a23 is nonzero. + + Finally, do d2 and d3 computation, but only if a01 is nonzero. + + Fall through to returning 0 (this will happen when both lines are + horizontal and they overlap). + */ +static int +x_order (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3) +{ + double a01, b01, c01; + double a23, b23, c23; + double d0, d1, d2, d3; + + if (z0.y == z1.y) + { + if (z2.y == z3.y) + { + double x01min, x01max; + double x23min, x23max; + + if (z0.x > z1.x) + { + x01min = z1.x; + x01max = z0.x; + } + else + { + x01min = z0.x; + x01max = z1.x; + } + + if (z2.x > z3.x) + { + x23min = z3.x; + x23max = z2.x; + } + else + { + x23min = z2.x; + x23max = z3.x; + } + + if (x23min >= x01max) return 1; + else if (x01min >= x23max) return -1; + else return 0; + } + else + { + /* z0-z1 is horizontal, z2-z3 isn't */ + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (z3.y < z2.y) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + + d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23); + d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23); + + if (d0 > 0) + { + if (d1 >= 0) return 1; + else return 0; + } + else if (d0 == 0) + { + if (d1 > 0) return 1; + else if (d1 < 0) return -1; + else printf ("case 1 degenerate\n"); + return 0; + } + else /* d0 < 0 */ + { + if (d1 <= 0) return -1; + else return 0; + } + } + } + else if (z2.y == z3.y) + { + /* z2-z3 is horizontal, z0-z1 isn't */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = (z1.x * z0.y - z1.y * z0.x) */ + + if (z1.y < z0.y) + { + a01 = -a01; + b01 = -b01; + c01 = -c01; + } + + d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01); + d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01); + + if (d2 > 0) + { + if (d3 >= 0) return -1; + else return 0; + } + else if (d2 == 0) + { + if (d3 > 0) return -1; + else if (d3 < 0) return 1; + else printf ("case 2 degenerate\n"); + return 0; + } + else /* d2 < 0 */ + { + if (d3 <= 0) return 1; + else return 0; + } + } + + /* find line equations ax + by + c = 0 */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = -(z1.x * z0.y - z1.y * z0.x) */ + + if (a01 > 0) + { + a01 = -a01; + b01 = -b01; + c01 = -c01; + } + /* so now, (a01, b01) points to the left, thus a01 * x + b01 * y + c01 + is negative if the point lies to the right of the line */ + + d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01); + d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01); + if (d2 > 0) + { + if (d3 >= 0) return -1; + } + else if (d2 == 0) + { + if (d3 > 0) return -1; + else if (d3 < 0) return 1; + else + fprintf (stderr, "colinear!\n"); + } + else /* d2 < 0 */ + { + if (d3 <= 0) return 1; + } + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (a23 > 0) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23); + d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23); + if (d0 > 0) + { + if (d1 >= 0) return 1; + } + else if (d0 == 0) + { + if (d1 > 0) return 1; + else if (d1 < 0) return -1; + else + fprintf (stderr, "colinear!\n"); + } + else /* d0 < 0 */ + { + if (d1 <= 0) return -1; + } + + return 0; +} + +/* similar to x_order, but to determine whether point z0 + epsilon lies to + the left of the line z2-z3 or to the right */ +static int +x_order_2 (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3) +{ + double a23, b23, c23; + double d0, d1; + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (a23 > 0) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + + d0 = a23 * z0.x + b23 * z0.y + c23; + + if (d0 > EPSILON) + return -1; + else if (d0 < -EPSILON) + return 1; + + d1 = a23 * z1.x + b23 * z1.y + c23; + if (d1 > EPSILON) + return -1; + else if (d1 < -EPSILON) + return 1; + + if (z0.x == z1.x && z1.x == z2.x && z2.x == z3.x) + { + art_dprint ("x_order_2: colinear and horizontally aligned!\n"); + return 0; + } + + if (z0.x <= z2.x && z1.x <= z2.x && z0.x <= z3.x && z1.x <= z3.x) + return -1; + if (z0.x >= z2.x && z1.x >= z2.x && z0.x >= z3.x && z1.x >= z3.x) + return 1; + + fprintf (stderr, "x_order_2: colinear!\n"); + return 0; +} + +#ifdef DEAD_CODE +/* Traverse the vector path, keeping it in x-sorted order. + + This routine doesn't actually do anything - it's just here for + explanatory purposes. */ +void +traverse (ArtSVP *vp) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { + printf ("y = %g\n", y); + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { + printf ("deleting %d\n", asi); + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { + cursor[seg_idx] = 0; + printf ("inserting %d\n", seg_idx); + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order (vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1], + vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1]) == -1) + break; + } + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + seg_idx++; + } + + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d (%g, %g) - (%g, %g) %s\n", asi, + vp->segs[asi].points[cursor[asi]].x, + vp->segs[asi].points[cursor[asi]].y, + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^"); + } + + /* advance y to the next event */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + y = vp->segs[asi].points[cursor[asi] + 1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (y > vp->segs[asi].points[cursor[asi] + 1].y) + y = vp->segs[asi].points[cursor[asi] + 1].y; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + while (cursor[asi] < vp->segs[asi].n_points - 1 && + y >= vp->segs[asi].points[cursor[asi] + 1].y) + cursor[asi]++; + } + printf ("\n"); + } + art_free (cursor); + art_free (active_segs); +} +#endif + +/* I believe that the loop will always break with i=1. + + I think I'll want to change this from a simple sorted list to a + modified stack. ips[*][0] will get its own data structure, and + ips[*] will in general only be allocated if there is an intersection. + Finally, the segment can be traced through the initial point + (formerly ips[*][0]), backwards through the stack, and finally + to cursor + 1. + + This change should cut down on allocation bandwidth, and also + eliminate the iteration through n_ipl below. + +*/ +static void +insert_ip (int seg_i, int *n_ips, int *n_ips_max, ArtPoint **ips, ArtPoint ip) +{ + int i; + ArtPoint tmp1, tmp2; + int n_ipl; + ArtPoint *ipl; + + n_ipl = n_ips[seg_i]++; + if (n_ipl == n_ips_max[seg_i]) + art_expand (ips[seg_i], ArtPoint, n_ips_max[seg_i]); + ipl = ips[seg_i]; + for (i = 1; i < n_ipl; i++) + if (ipl[i].y > ip.y) + break; + tmp1 = ip; + for (; i <= n_ipl; i++) + { + tmp2 = ipl[i]; + ipl[i] = tmp1; + tmp1 = tmp2; + } +} + +/* test active segment (i - 1) against i for intersection, if + so, add intersection point to both ips lists. */ +static void +intersect_neighbors (int i, int *active_segs, + int *n_ips, int *n_ips_max, ArtPoint **ips, + int *cursor, ArtSVP *vp) +{ + ArtPoint z0, z1, z2, z3; + int asi01, asi23; + ArtPoint ip; + + asi01 = active_segs[i - 1]; + + z0 = ips[asi01][0]; + if (n_ips[asi01] == 1) + z1 = vp->segs[asi01].points[cursor[asi01] + 1]; + else + z1 = ips[asi01][1]; + + asi23 = active_segs[i]; + + z2 = ips[asi23][0]; + if (n_ips[asi23] == 1) + z3 = vp->segs[asi23].points[cursor[asi23] + 1]; + else + z3 = ips[asi23][1]; + + if (intersect_lines (z0, z1, z2, z3, &ip)) + { +#ifdef VERBOSE + printf ("new intersection point: (%g, %g)\n", ip.x, ip.y); +#endif + insert_ip (asi01, n_ips, n_ips_max, ips, ip); + insert_ip (asi23, n_ips, n_ips_max, ips, ip); + } +} + +/* Add a new point to a segment in the svp. + + Here, we also check to make sure that the segments satisfy nocross. + However, this is only valuable for debugging, and could possibly be + removed. +*/ +static void +svp_add_point (ArtSVP *svp, int *n_points_max, + ArtPoint p, int *seg_map, int *active_segs, int n_active_segs, + int i) +{ + int asi, asi_left, asi_right; + int n_points, n_points_left, n_points_right; + ArtSVPSeg *seg; + + asi = seg_map[active_segs[i]]; + seg = &svp->segs[asi]; + n_points = seg->n_points; + /* find out whether neighboring segments share a point */ + if (i > 0) + { + asi_left = seg_map[active_segs[i - 1]]; + n_points_left = svp->segs[asi_left].n_points; + if (n_points_left > 1 && + PT_EQ (svp->segs[asi_left].points[n_points_left - 2], + svp->segs[asi].points[n_points - 1])) + { + /* ok, new vector shares a top point with segment to the left - + now, check that it satisfies ordering invariant */ + if (x_order (svp->segs[asi_left].points[n_points_left - 2], + svp->segs[asi_left].points[n_points_left - 1], + svp->segs[asi].points[n_points - 1], + p) < 1) + + { +#ifdef VERBOSE + printf ("svp_add_point: cross on left!\n"); +#endif + } + } + } + + if (i + 1 < n_active_segs) + { + asi_right = seg_map[active_segs[i + 1]]; + n_points_right = svp->segs[asi_right].n_points; + if (n_points_right > 1 && + PT_EQ (svp->segs[asi_right].points[n_points_right - 2], + svp->segs[asi].points[n_points - 1])) + { + /* ok, new vector shares a top point with segment to the right - + now, check that it satisfies ordering invariant */ + if (x_order (svp->segs[asi_right].points[n_points_right - 2], + svp->segs[asi_right].points[n_points_right - 1], + svp->segs[asi].points[n_points - 1], + p) > -1) + { +#ifdef VERBOSE + printf ("svp_add_point: cross on right!\n"); +#endif + } + } + } + if (n_points_max[asi] == n_points) + art_expand (seg->points, ArtPoint, n_points_max[asi]); + seg->points[n_points] = p; + if (p.x < seg->bbox.x0) + seg->bbox.x0 = p.x; + else if (p.x > seg->bbox.x1) + seg->bbox.x1 = p.x; + seg->bbox.y1 = p.y; + seg->n_points++; +} + +#if 0 +/* find where the segment (currently at i) is supposed to go, and return + the target index - if equal to i, then there is no crossing problem. + + "Where it is supposed to go" is defined as following: + + Delete element i, re-insert at position target (bumping everything + target and greater to the right). + */ +static int +find_crossing (int i, int *active_segs, int n_active_segs, + int *cursor, ArtPoint **ips, int *n_ips, ArtSVP *vp) +{ + int asi, asi_left, asi_right; + ArtPoint p0, p1; + ArtPoint p0l, p1l; + ArtPoint p0r, p1r; + int target; + + asi = active_segs[i]; + p0 = ips[asi][0]; + if (n_ips[asi] == 1) + p1 = vp->segs[asi].points[cursor[asi] + 1]; + else + p1 = ips[asi][1]; + + for (target = i; target > 0; target--) + { + asi_left = active_segs[target - 1]; + p0l = ips[asi_left][0]; + if (n_ips[asi_left] == 1) + p1l = vp->segs[asi_left].points[cursor[asi_left] + 1]; + else + p1l = ips[asi_left][1]; + if (!PT_EQ (p0, p0l)) + break; + +#ifdef VERBOSE + printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n", + p0l.x, p0l.y, p1l.x, p1l.y, p0.x, p0.y, p1.x, p1.y); +#endif + if (x_order (p0l, p1l, p0, p1) == 1) + break; + +#ifdef VERBOSE + printf ("scanning to the left (i=%d, target=%d)\n", i, target); +#endif + } + + if (target < i) return target; + + for (; target < n_active_segs - 1; target++) + { + asi_right = active_segs[target + 1]; + p0r = ips[asi_right][0]; + if (n_ips[asi_right] == 1) + p1r = vp->segs[asi_right].points[cursor[asi_right] + 1]; + else + p1r = ips[asi_right][1]; + if (!PT_EQ (p0, p0r)) + break; + +#ifdef VERBOSE + printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n", + p0.x, p0.y, p1.x, p1.y, p0r.x, p0r.y, p1r.x, p1r.y); +#endif + if (x_order (p0r, p1r, p0, p1) == 1) + break; + +#ifdef VERBOSE + printf ("scanning to the right (i=%d, target=%d)\n", i, target); +#endif + } + + return target; +} +#endif + +/* This routine handles the case where the segment changes its position + in the active segment list. Generally, this will happen when the + segment (defined by i and cursor) shares a top point with a neighbor, + but breaks the ordering invariant. + + Essentially, this routine sorts the lines [start..end), all of which + share a top point. This is implemented as your basic insertion sort. + + This routine takes care of intersecting the appropriate neighbors, + as well. + + A first argument of -1 immediately returns, which helps reduce special + casing in the main unwind routine. +*/ +static void +fix_crossing (int start, int end, int *active_segs, int n_active_segs, + int *cursor, ArtPoint **ips, int *n_ips, int *n_ips_max, + ArtSVP *vp, int *seg_map, + ArtSVP **p_new_vp, int *pn_segs_max, + int **pn_points_max) +{ + int i, j; + int target; + int asi, asj; + ArtPoint p0i, p1i; + ArtPoint p0j, p1j; + int swap = 0; +#ifdef VERBOSE + int k; +#endif + ArtPoint *pts; + +#ifdef VERBOSE + printf ("fix_crossing: [%d..%d)", start, end); + for (k = 0; k < n_active_segs; k++) + printf (" %d", active_segs[k]); + printf ("\n"); +#endif + + if (start == -1) + return; + + for (i = start + 1; i < end; i++) + { + + asi = active_segs[i]; + if (cursor[asi] < vp->segs[asi].n_points - 1) { + p0i = ips[asi][0]; + if (n_ips[asi] == 1) + p1i = vp->segs[asi].points[cursor[asi] + 1]; + else + p1i = ips[asi][1]; + + for (j = i - 1; j >= start; j--) + { + asj = active_segs[j]; + if (cursor[asj] < vp->segs[asj].n_points - 1) + { + p0j = ips[asj][0]; + if (n_ips[asj] == 1) + p1j = vp->segs[asj].points[cursor[asj] + 1]; + else + p1j = ips[asj][1]; + + /* we _hope_ p0i = p0j */ + if (x_order_2 (p0j, p1j, p0i, p1i) == -1) + break; + } + } + + target = j + 1; + /* target is where active_seg[i] _should_ be in active_segs */ + + if (target != i) + { + swap = 1; + +#ifdef VERBOSE + printf ("fix_crossing: at %i should be %i\n", i, target); +#endif + + /* let's close off all relevant segments */ + for (j = i; j >= target; j--) + { + asi = active_segs[j]; + /* First conjunct: this isn't the last point in the original + segment. + + Second conjunct: this isn't the first point in the new + segment (i.e. already broken). + */ + if (cursor[asi] < vp->segs[asi].n_points - 1 && + (*p_new_vp)->segs[seg_map[asi]].n_points != 1) + { + int seg_num; + /* so break here */ +#ifdef VERBOSE + printf ("closing off %d\n", j); +#endif + + pts = art_new (ArtPoint, 16); + pts[0] = ips[asi][0]; + seg_num = art_svp_add_segment (p_new_vp, pn_segs_max, + pn_points_max, + 1, vp->segs[asi].dir, + pts, + NULL); + (*pn_points_max)[seg_num] = 16; + seg_map[asi] = seg_num; + } + } + + /* now fix the ordering in active_segs */ + asi = active_segs[i]; + for (j = i; j > target; j--) + active_segs[j] = active_segs[j - 1]; + active_segs[j] = asi; + } + } + } + if (swap && start > 0) + { + int as_start; + + as_start = active_segs[start]; + if (cursor[as_start] < vp->segs[as_start].n_points) + { +#ifdef VERBOSE + printf ("checking intersection of %d, %d\n", start - 1, start); +#endif + intersect_neighbors (start, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + + if (swap && end < n_active_segs) + { + int as_end; + + as_end = active_segs[end - 1]; + if (cursor[as_end] < vp->segs[as_end].n_points) + { +#ifdef VERBOSE + printf ("checking intersection of %d, %d\n", end - 1, end); +#endif + intersect_neighbors (end, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + if (swap) + { +#ifdef VERBOSE + printf ("fix_crossing return: [%d..%d)", start, end); + for (k = 0; k < n_active_segs; k++) + printf (" %d", active_segs[k]); + printf ("\n"); +#endif + } +} + +/* Return a new sorted vector that covers the same area as the + argument, but which satisfies the nocross invariant. + + Basically, this routine works by finding the intersection points, + and cutting the segments at those points. + + Status of this routine: + + Basic correctness: Seems ok. + + Numerical stability: known problems in the case of points falling + on lines, and colinear lines. For actual use, randomly perturbing + the vertices is currently recommended. + + Speed: pretty good, although a more efficient priority queue, as + well as bbox culling of potential intersections, are two + optimizations that could help. + + Precision: pretty good, although the numerical stability problems + make this routine unsuitable for precise calculations of + differences. + +*/ + +/* Here is a more detailed description of the algorithm. It follows + roughly the structure of traverse (above), but is obviously quite + a bit more complex. + + Here are a few important data structures: + + A new sorted vector path (new_svp). + + For each (active) segment in the original, a list of intersection + points. + + Of course, the original being traversed. + + The following invariants hold (in addition to the invariants + of the traverse procedure). + + The new sorted vector path lies entirely above the y scan line. + + The new sorted vector path keeps the nocross invariant. + + For each active segment, the y scan line crosses the line from the + first to the second of the intersection points (where the second + point is cursor + 1 if there is only one intersection point). + + The list of intersection points + the (cursor + 1) point is kept + in nondecreasing y order. + + Of the active segments, none of the lines from first to second + intersection point cross the 1st ip..2nd ip line of the left or + right neighbor. (However, such a line may cross further + intersection points of the neighbors, or segments past the + immediate neighbors). + + Of the active segments, all lines from 1st ip..2nd ip are in + strictly increasing x_order (this is very similar to the invariant + of the traverse procedure, but is explicitly stated here in terms + of ips). (this basically says that nocross holds on the active + segments) + + The combination of the new sorted vector path, the path through all + the intersection points to cursor + 1, and [cursor + 1, n_points) + covers the same area as the argument. + + Another important data structure is mapping from original segment + number to new segment number. + + The algorithm is perhaps best understood as advancing the cursors + while maintaining these invariants. Here's roughly how it's done. + + When deleting segments from the active list, those segments are added + to the new sorted vector path. In addition, the neighbors may intersect + each other, so they are intersection tested (see below). + + When inserting new segments, they are intersection tested against + their neighbors. The top point of the segment becomes the first + intersection point. + + Advancing the cursor is just a bit different from the traverse + routine, as the cursor may advance through the intersection points + as well. Only when there is a single intersection point in the list + does the cursor advance in the original segment. In either case, + the new vector is intersection tested against both neighbors. It + also causes the vector over which the cursor is advancing to be + added to the new svp. + + Two steps need further clarification: + + Intersection testing: the 1st ip..2nd ip lines of the neighbors + are tested to see if they cross (using intersect_lines). If so, + then the intersection point is added to the ip list of both + segments, maintaining the invariant that the list of intersection + points is nondecreasing in y). + + Adding vector to new svp: if the new vector shares a top x + coordinate with another vector, then it is checked to see whether + it is in order. If not, then both segments are "broken," and then + restarted. Note: in the case when both segments are in the same + order, they may simply be swapped without breaking. + + For the time being, I'm going to put some of these operations into + subroutines. If it turns out to be a performance problem, I could + try to reorganize the traverse procedure so that each is only + called once, and inline them. But if it's not a performance + problem, I'll just keep it this way, because it will probably help + to make the code clearer, and I believe this code could use all the + clarity it can get. */ +/** + * art_svp_uncross: Resolve self-intersections of an svp. + * @vp: The original svp. + * + * Finds all the intersections within @vp, and constructs a new svp + * with new points added at these intersections. + * + * This routine needs to be redone from scratch with numerical robustness + * in mind. I'm working on it. + * + * Return value: The new svp. + **/ +ArtSVP * +art_svp_uncross (ArtSVP *vp) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + /* new data structures */ + /* intersection points; invariant: *ips[i] is only allocated if + i is active */ + int *n_ips, *n_ips_max; + ArtPoint **ips; + /* new sorted vector path */ + int n_segs_max, seg_num; + ArtSVP *new_vp; + int *n_points_max; + /* mapping from argument to new segment numbers - again, only valid + if active */ + int *seg_map; + double y_curs; + ArtPoint p_curs; + int first_share; + double share_x; + ArtPoint *pts; + + n_segs_max = 16; + new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + new_vp->n_segs = 0; + + if (vp->n_segs == 0) + return new_vp; + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + seg_map = art_new (int, vp->n_segs); + n_ips = art_new (int, vp->n_segs); + n_ips_max = art_new (int, vp->n_segs); + ips = art_new (ArtPoint *, vp->n_segs); + + n_points_max = art_new (int, n_segs_max); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { +#ifdef VERBOSE + printf ("y = %g\n", y); +#endif + + /* maybe move deletions to end of loop (to avoid so much special + casing on the end of a segment)? */ + + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { + do + { +#ifdef VERBOSE + printf ("deleting %d\n", asi); +#endif + art_free (ips[asi]); + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + if (i < n_active_segs) + asi = active_segs[i]; + else + break; + } + while (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y); + + /* test intersection of neighbors */ + if (i > 0 && i < n_active_segs) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { +#ifdef VERBOSE + printf ("inserting %d\n", seg_idx); +#endif + cursor[seg_idx] = 0; + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order_2 (vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1], + vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1]) == -1) + break; + } + + /* Create and initialize the intersection points data structure */ + n_ips[seg_idx] = 1; + n_ips_max[seg_idx] = 2; + ips[seg_idx] = art_new (ArtPoint, n_ips_max[seg_idx]); + ips[seg_idx][0] = vp->segs[seg_idx].points[0]; + + /* Start a new segment in the new vector path */ + pts = art_new (ArtPoint, 16); + pts[0] = vp->segs[seg_idx].points[0]; + seg_num = art_svp_add_segment (&new_vp, &n_segs_max, + &n_points_max, + 1, vp->segs[seg_idx].dir, + pts, + NULL); + n_points_max[seg_num] = 16; + seg_map[seg_idx] = seg_num; + + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + + if (i > 0) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + if (i + 1 < n_active_segs) + intersect_neighbors (i + 1, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + seg_idx++; + } + + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ +#ifdef VERBOSE + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d ", asi); + for (j = 0; j < n_ips[asi]; j++) + printf ("(%g, %g) - ", + ips[asi][j].x, + ips[asi][j].y); + printf ("(%g, %g) %s\n", + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^"); + } +#endif + + /* advance y to the next event + Note: this is quadratic. We'd probably get decent constant + factor speed improvement by caching the y_curs values. */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + if (n_ips[asi] == 1) + y = vp->segs[asi].points[cursor[asi] + 1].y; + else + y = ips[asi][1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (n_ips[asi] == 1) + y_curs = vp->segs[asi].points[cursor[asi] + 1].y; + else + y_curs = ips[asi][1].y; + if (y > y_curs) + y = y_curs; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + first_share = -1; + share_x = 0; /* to avoid gcc warning, although share_x is never + used when first_share is -1 */ + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (n_ips[asi] == 1) + p_curs = vp->segs[asi].points[cursor[asi] + 1]; + else + p_curs = ips[asi][1]; + if (p_curs.y == y) + { + svp_add_point (new_vp, n_points_max, + p_curs, seg_map, active_segs, n_active_segs, i); + + n_ips[asi]--; + for (j = 0; j < n_ips[asi]; j++) + ips[asi][j] = ips[asi][j + 1]; + + if (n_ips[asi] == 0) + { + ips[asi][0] = p_curs; + n_ips[asi] = 1; + cursor[asi]++; + } + + if (first_share < 0 || p_curs.x != share_x) + { + /* this is where crossings are detected, and if + found, the active segments switched around. */ + + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + + first_share = i; + share_x = p_curs.x; + } + + if (cursor[asi] < vp->segs[asi].n_points - 1) + { + + if (i > 0) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + if (i + 1 < n_active_segs) + intersect_neighbors (i + 1, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + else + { + /* not on a cursor point */ + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + first_share = -1; + } + } + + /* fix crossing on last shared group */ + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + +#ifdef VERBOSE + printf ("\n"); +#endif + } + + /* not necessary to sort, new segments only get added at y, which + increases monotonically */ +#if 0 + qsort (&new_vp->segs, new_vp->n_segs, sizeof (svp_seg), svp_seg_compare); + { + int k; + for (k = 0; k < new_vp->n_segs - 1; k++) + { + printf ("(%g, %g) - (%g, %g) %s (%g, %g) - (%g, %g)\n", + new_vp->segs[k].points[0].x, + new_vp->segs[k].points[0].y, + new_vp->segs[k].points[1].x, + new_vp->segs[k].points[1].y, + svp_seg_compare (&new_vp->segs[k], &new_vp->segs[k + 1]) > 1 ? ">": "<", + new_vp->segs[k + 1].points[0].x, + new_vp->segs[k + 1].points[0].y, + new_vp->segs[k + 1].points[1].x, + new_vp->segs[k + 1].points[1].y); + } + } +#endif + + art_free (n_points_max); + art_free (seg_map); + art_free (n_ips_max); + art_free (n_ips); + art_free (ips); + art_free (cursor); + art_free (active_segs); + + return new_vp; +} + +#define noVERBOSE + +/* Rewind a svp satisfying the nocross invariant. + + The winding number of a segment is defined as the winding number of + the points to the left while travelling in the direction of the + segment. Therefore it preincrements and postdecrements as a scan + line is traversed from left to right. + + Status of this routine: + + Basic correctness: Was ok in gfonted. However, this code does not + yet compute bboxes for the resulting svp segs. + + Numerical stability: known problems in the case of horizontal + segments in polygons with any complexity. For actual use, randomly + perturbing the vertices is recommended. + + Speed: good. + + Precision: good, except that no attempt is made to remove "hair". + Doing random perturbation just makes matters worse. + +*/ +/** + * art_svp_rewind_uncrossed: Rewind an svp satisfying the nocross invariant. + * @vp: The original svp. + * @rule: The winding rule. + * + * Creates a new svp with winding number of 0 or 1 everywhere. The @rule + * argument specifies a rule for how winding numbers in the original + * @vp map to the winding numbers in the result. + * + * With @rule == ART_WIND_RULE_NONZERO, the resulting svp has a + * winding number of 1 where @vp has a nonzero winding number. + * + * With @rule == ART_WIND_RULE_INTERSECT, the resulting svp has a + * winding number of 1 where @vp has a winding number greater than + * 1. It is useful for computing intersections. + * + * With @rule == ART_WIND_RULE_ODDEVEN, the resulting svp has a + * winding number of 1 where @vp has an odd winding number. It is + * useful for implementing the even-odd winding rule of the + * PostScript imaging model. + * + * With @rule == ART_WIND_RULE_POSITIVE, the resulting svp has a + * winding number of 1 where @vp has a positive winding number. It is + * useful for implementing asymmetric difference. + * + * This routine needs to be redone from scratch with numerical robustness + * in mind. I'm working on it. + * + * Return value: The new svp. + **/ +ArtSVP * +art_svp_rewind_uncrossed (ArtSVP *vp, ArtWindRule rule) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + + ArtSVP *new_vp; + int n_segs_max; + int *winding; + int left_wind; + int wind; + int keep, invert; + +#ifdef VERBOSE + print_svp (vp); +#endif + n_segs_max = 16; + new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + new_vp->n_segs = 0; + + if (vp->n_segs == 0) + return new_vp; + + winding = art_new (int, vp->n_segs); + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { +#ifdef VERBOSE + printf ("y = %g\n", y); +#endif + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { +#ifdef VERBOSE + printf ("deleting %d\n", asi); +#endif + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { +#ifdef VERBOSE + printf ("inserting %d\n", seg_idx); +#endif + cursor[seg_idx] = 0; + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order_2 (vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1], + vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1]) == -1) + break; + } + + /* Determine winding number for this segment */ + if (i == 0) + left_wind = 0; + else if (vp->segs[active_segs[i - 1]].dir) + left_wind = winding[active_segs[i - 1]]; + else + left_wind = winding[active_segs[i - 1]] - 1; + + if (vp->segs[seg_idx].dir) + wind = left_wind + 1; + else + wind = left_wind; + + winding[seg_idx] = wind; + + switch (rule) + { + case ART_WIND_RULE_NONZERO: + keep = (wind == 1 || wind == 0); + invert = (wind == 0); + break; + case ART_WIND_RULE_INTERSECT: + keep = (wind == 2); + invert = 0; + break; + case ART_WIND_RULE_ODDEVEN: + keep = 1; + invert = !(wind & 1); + break; + case ART_WIND_RULE_POSITIVE: + keep = (wind == 1); + invert = 0; + break; + default: + keep = 0; + invert = 0; + break; + } + + if (keep) + { + ArtPoint *points, *new_points; + int n_points; + int new_dir; + +#ifdef VERBOSE + printf ("keeping segment %d\n", seg_idx); +#endif + n_points = vp->segs[seg_idx].n_points; + points = vp->segs[seg_idx].points; + new_points = art_new (ArtPoint, n_points); + memcpy (new_points, points, n_points * sizeof (ArtPoint)); + new_dir = vp->segs[seg_idx].dir ^ invert; + art_svp_add_segment (&new_vp, &n_segs_max, + NULL, + n_points, new_dir, new_points, + &vp->segs[seg_idx].bbox); + } + + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + seg_idx++; + } + +#ifdef VERBOSE + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d:%d (%g, %g) - (%g, %g) %s %d\n", asi, + cursor[asi], + vp->segs[asi].points[cursor[asi]].x, + vp->segs[asi].points[cursor[asi]].y, + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^", + winding[asi]); + } +#endif + + /* advance y to the next event */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + y = vp->segs[asi].points[cursor[asi] + 1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (y > vp->segs[asi].points[cursor[asi] + 1].y) + y = vp->segs[asi].points[cursor[asi] + 1].y; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + while (cursor[asi] < vp->segs[asi].n_points - 1 && + y >= vp->segs[asi].points[cursor[asi] + 1].y) + cursor[asi]++; + } +#ifdef VERBOSE + printf ("\n"); +#endif + } + art_free (cursor); + art_free (active_segs); + art_free (winding); + + return new_vp; +} diff --git a/libart_lgpl/art_svp_wind.h b/libart_lgpl/art_svp_wind.h new file mode 100644 index 0000000000..0e9d5e70d4 --- /dev/null +++ b/libart_lgpl/art_svp_wind.h @@ -0,0 +1,53 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_SVP_WIND_H__ +#define __ART_SVP_WIND_H__ + +/* Primitive intersection and winding number operations on sorted + vector paths. */ + +#include <libart_lgpl/art_svp.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef ART_WIND_RULE_DEFINED +#define ART_WIND_RULE_DEFINED +typedef enum { + ART_WIND_RULE_NONZERO, + ART_WIND_RULE_INTERSECT, + ART_WIND_RULE_ODDEVEN, + ART_WIND_RULE_POSITIVE +} ArtWindRule; +#endif + +ArtSVP * +art_svp_uncross (ArtSVP *vp); + +ArtSVP * +art_svp_rewind_uncrossed (ArtSVP *vp, ArtWindRule rule); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_SVP_WIND_H__ */ diff --git a/libart_lgpl/art_uta.c b/libart_lgpl/art_uta.c new file mode 100644 index 0000000000..10bd6ee3bd --- /dev/null +++ b/libart_lgpl/art_uta.c @@ -0,0 +1,88 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta.h" + +#include <string.h> +#include "art_misc.h" + +/** + * art_uta_new: Allocate a new uta. + * @x0: Left coordinate of uta. + * @y0: Top coordinate of uta. + * @x1: Right coordinate of uta. + * @y1: Bottom coordinate of uta. + * + * Allocates a new microtile array. The arguments are in units of + * tiles, not pixels. + * + * Returns: the newly allocated #ArtUta. + **/ +ArtUta * +art_uta_new (int x0, int y0, int x1, int y1) +{ + ArtUta *uta; + + uta = art_new (ArtUta, 1); + uta->x0 = x0; + uta->y0 = y0; + uta->width = x1 - x0; + uta->height = y1 - y0; + + uta->utiles = art_new (ArtUtaBbox, uta->width * uta->height); + + memset (uta->utiles, 0, uta->width * uta->height * sizeof(ArtUtaBbox)); + return uta; + } + +/** + * art_uta_new_coords: Allocate a new uta, based on pixel coordinates. + * @x0: Left coordinate of uta. + * @y0: Top coordinate of uta. + * @x1: Right coordinate of uta. + * @y1: Bottom coordinate of uta. + * + * Allocates a new microtile array. The arguments are in pixels + * + * Returns: the newly allocated #ArtUta. + **/ +ArtUta * +art_uta_new_coords (int x0, int y0, int x1, int y1) +{ + return art_uta_new (x0 >> ART_UTILE_SHIFT, y0 >> ART_UTILE_SHIFT, + 1 + (x1 >> ART_UTILE_SHIFT), + 1 + (y1 >> ART_UTILE_SHIFT)); +} + +/** + * art_uta_free: Free a uta. + * @uta: The uta to free. + * + * Frees the microtile array structure, including the actual microtile + * data. + **/ +void +art_uta_free (ArtUta *uta) +{ + art_free (uta->utiles); + art_free (uta); +} + +/* User to Aardvark! */ diff --git a/libart_lgpl/art_uta.h b/libart_lgpl/art_uta.h new file mode 100644 index 0000000000..e0247c3fc9 --- /dev/null +++ b/libart_lgpl/art_uta.h @@ -0,0 +1,68 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_UTA_H__ +#define __ART_UTA_H__ + +/* Basic data structures and constructors for microtile arrays */ + +#include <libart_lgpl/art_misc.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef art_u32 ArtUtaBbox; +typedef struct _ArtUta ArtUta; + +#define ART_UTA_BBOX_CONS(x0, y0, x1, y1) (((x0) << 24) | ((y0) << 16) | \ + ((x1) << 8) | (y1)) + +#define ART_UTA_BBOX_X0(ub) ((ub) >> 24) +#define ART_UTA_BBOX_Y0(ub) (((ub) >> 16) & 0xff) +#define ART_UTA_BBOX_X1(ub) (((ub) >> 8) & 0xff) +#define ART_UTA_BBOX_Y1(ub) ((ub) & 0xff) + +#define ART_UTILE_SHIFT 5 +#define ART_UTILE_SIZE (1 << ART_UTILE_SHIFT) + +/* Coordinates are shifted right by ART_UTILE_SHIFT wrt the real + coordinates. */ +struct _ArtUta { + int x0; + int y0; + int width; + int height; + ArtUtaBbox *utiles; +}; + +ArtUta * +art_uta_new (int x0, int y0, int x1, int y1); + +ArtUta * +art_uta_new_coords (int x0, int y0, int x1, int y1); + +void +art_uta_free (ArtUta *uta); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_UTA_H__ */ diff --git a/libart_lgpl/art_uta_ops.c b/libart_lgpl/art_uta_ops.c new file mode 100644 index 0000000000..1b4251607b --- /dev/null +++ b/libart_lgpl/art_uta_ops.c @@ -0,0 +1,112 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_ops.h" + +#include <string.h> +#include "art_misc.h" +#include "art_uta.h" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** + * art_uta_union: Compute union of two uta's. + * @uta1: One uta. + * @uta2: The other uta. + * + * Computes the union of @uta1 and @uta2. The union is approximate, + * but coverage is guaranteed over all pixels included in either of + * the arguments, ie more pixels may be covered than the "exact" + * union. + * + * Note: this routine is used in the Gnome Canvas to accumulate the + * region that needs to be repainted. However, since it copies over + * the entire uta (which might be largish) even when the update may be + * small, it can be a performance bottleneck. There are two approaches + * to this problem, both of which are probably worthwhile. First, the + * generated uta's should always be limited to the visible window, + * thus guaranteeing that uta's never become large. Second, there + * should be a new, destructive union operation that only touches a + * small part of the uta when the update is small. + * + * Return value: The new union uta. + **/ +ArtUta * +art_uta_union (ArtUta *uta1, ArtUta *uta2) +{ + ArtUta *uta; + int x0, y0, x1, y1; + int x, y; + int ix, ix1, ix2; + ArtUtaBbox bb, bb1, bb2; + + x0 = MIN(uta1->x0, uta2->x0); + y0 = MIN(uta1->y0, uta2->y0); + x1 = MAX(uta1->x0 + uta1->width, uta2->x0 + uta2->width); + y1 = MAX(uta1->y0 + uta1->height, uta2->y0 + uta2->height); + uta = art_uta_new (x0, y0, x1, y1); + + /* could move the first two if/else statements out of the loop */ + ix = 0; + for (y = y0; y < y1; y++) + { + ix1 = (y - uta1->y0) * uta1->width + x0 - uta1->x0; + ix2 = (y - uta2->y0) * uta2->width + x0 - uta2->x0; + for (x = x0; x < x1; x++) + { + if (x < uta1->x0 || y < uta1->y0 || + x >= uta1->x0 + uta1->width || y >= uta1->y0 + uta1->height) + bb1 = 0; + else + bb1 = uta1->utiles[ix1]; + + if (x < uta2->x0 || y < uta2->y0 || + x >= uta2->x0 + uta2->width || y >= uta2->y0 + uta2->height) + bb2 = 0; + else + bb2 = uta2->utiles[ix2]; + + if (bb1 == 0) + bb = bb2; + else if (bb2 == 0) + bb = bb1; + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb1), + ART_UTA_BBOX_X0(bb2)), + MIN(ART_UTA_BBOX_Y0(bb1), + ART_UTA_BBOX_Y0(bb2)), + MAX(ART_UTA_BBOX_X1(bb1), + ART_UTA_BBOX_X1(bb2)), + MAX(ART_UTA_BBOX_Y1(bb1), + ART_UTA_BBOX_Y1(bb2))); + uta->utiles[ix] = bb; + ix++; + ix1++; + ix2++; + } + } + return uta; +} diff --git a/libart_lgpl/art_uta_ops.h b/libart_lgpl/art_uta_ops.h new file mode 100644 index 0000000000..fc19d012ee --- /dev/null +++ b/libart_lgpl/art_uta_ops.h @@ -0,0 +1,38 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_UTA_OPS_H__ +#define __ART_UTA_OPS_H__ + +/* Basic operations on microtile arrays */ + +#include <libart_lgpl/art_uta.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtUta * +art_uta_union (ArtUta *uta1, ArtUta *uta2); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_UTA_OPS_H__ */ diff --git a/libart_lgpl/art_uta_rect.c b/libart_lgpl/art_uta_rect.c new file mode 100644 index 0000000000..68a6053459 --- /dev/null +++ b/libart_lgpl/art_uta_rect.c @@ -0,0 +1,111 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_rect.h" + +#include "art_misc.h" +#include "art_uta.h" +#include "art_rect.h" + +/** + * art_uta_from_irect: Generate uta covering a rectangle. + * @bbox: The source rectangle. + * + * Generates a uta exactly covering @bbox. Please do not call this + * function with a @bbox with zero height or width. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_irect (ArtIRect *bbox) +{ + ArtUta *uta; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int width, height; + int x, y; + int xf0, yf0, xf1, yf1; + int ix; + + uta = art_new (ArtUta, 1); + uta->x0 = bbox->x0 >> ART_UTILE_SHIFT; + uta->y0 = bbox->y0 >> ART_UTILE_SHIFT; + width = ((bbox->x1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->x0; + height = ((bbox->y1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->y0; + utiles = art_new (ArtUtaBbox, width * height); + + uta->width = width; + uta->height = height; + uta->utiles = utiles; + + xf0 = bbox->x0 & (ART_UTILE_SIZE - 1); + yf0 = bbox->y0 & (ART_UTILE_SIZE - 1); + xf1 = ((bbox->x1 - 1) & (ART_UTILE_SIZE - 1)) + 1; + yf1 = ((bbox->y1 - 1) & (ART_UTILE_SIZE - 1)) + 1; + if (height == 1) + { + if (width == 1) + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, yf1); + else + { + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, yf1); + bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, yf1); + } + } + else + { + if (width == 1) + { + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (xf0, 0, xf1, ART_UTILE_SIZE); + for (y = 1; y < height - 1; y++) + utiles[y] = bb; + utiles[y] = ART_UTA_BBOX_CONS (xf0, 0, xf1, yf1); + } + else + { + utiles[0] = + ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, ART_UTILE_SIZE); + ix = width; + for (y = 1; y < height - 1; y++) + { + utiles[ix++] = + ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, ART_UTILE_SIZE); + } + utiles[ix++] = ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, yf1); + bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, yf1); + } + } + return uta; +} diff --git a/libart_lgpl/art_uta_rect.h b/libart_lgpl/art_uta_rect.h new file mode 100644 index 0000000000..cf726d81da --- /dev/null +++ b/libart_lgpl/art_uta_rect.h @@ -0,0 +1,37 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_UTA_RECT_H__ +#define __ART_UTA_RECT_H__ + +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_uta.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtUta * +art_uta_from_irect (ArtIRect *bbox); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_UTA_RECT_H__ */ diff --git a/libart_lgpl/art_uta_svp.c b/libart_lgpl/art_uta_svp.c new file mode 100644 index 0000000000..2a0f37250e --- /dev/null +++ b/libart_lgpl/art_uta_svp.c @@ -0,0 +1,54 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* LGPL Copyright 1998 Raph Levien <raph@acm.org> */ + +#include "config.h" +#include "art_uta_svp.h" + +#include "art_misc.h" +#include "art_vpath.h" +#include "art_uta.h" +#include "art_uta_vpath.h" +#include "art_svp.h" +#include "art_vpath_svp.h" + +/** + * art_uta_from_svp: Generate uta covering an svp. + * @svp: The source svp. + * + * Generates a uta covering @svp. The resulting uta is of course + * approximate, ie it may cover more pixels than covered by @svp. + * + * Note: I will want to replace this with a more direct + * implementation. But this gets the api in place. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_svp (const ArtSVP *svp) +{ + ArtVpath *vpath; + ArtUta *uta; + + vpath = art_vpath_from_svp (svp); + uta = art_uta_from_vpath (vpath); + art_free (vpath); + return uta; +} diff --git a/libart_lgpl/art_uta_svp.h b/libart_lgpl/art_uta_svp.h new file mode 100644 index 0000000000..0d2d489cb0 --- /dev/null +++ b/libart_lgpl/art_uta_svp.h @@ -0,0 +1,40 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_UTA_SVP_H__ +#define __ART_UTA_SVP_H__ + +/* Basic data structures and constructors for microtile arrays */ + +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_uta.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtUta * +art_uta_from_svp (const ArtSVP *svp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_UTA_SVP_H__ */ + diff --git a/libart_lgpl/art_uta_vpath.c b/libart_lgpl/art_uta_vpath.c new file mode 100644 index 0000000000..d7df5ede8b --- /dev/null +++ b/libart_lgpl/art_uta_vpath.c @@ -0,0 +1,382 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_vpath.h" + +#include <math.h> + +#include "art_misc.h" +#include "art_vpath.h" +#include "art_uta.h" + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/** + * art_uta_add_line: Add a line to the uta. + * @uta: The uta to modify. + * @x0: X coordinate of line start point. + * @y0: Y coordinate of line start point. + * @x1: X coordinate of line end point. + * @y1: Y coordinate of line end point. + * @rbuf: Buffer containing first difference of winding number. + * @rbuf_rowstride: Rowstride of @rbuf. + * + * Add the line (@x0, @y0) - (@x1, @y1) to @uta, and also update the + * winding number buffer used for rendering the interior. @rbuf + * contains the first partial difference (in the X direction) of the + * winding number, measured in grid cells. Thus, each time that a line + * crosses a horizontal uta grid line, an entry of @rbuf is + * incremented if @y1 > @y0, decremented otherwise. + * + * Note that edge handling is fairly delicate. Please rtfs for + * details. + **/ +void +art_uta_add_line (ArtUta *uta, double x0, double y0, double x1, double y1, + int *rbuf, int rbuf_rowstride) +{ + int xmin, ymin; + double xmax, ymax; + int xmaxf, ymaxf; + int xmaxc, ymaxc; + int xt0, yt0; + int xt1, yt1; + int xf0, yf0; + int xf1, yf1; + int ix, ix1; + ArtUtaBbox bb; + + xmin = floor (MIN(x0, x1)); + xmax = MAX(x0, x1); + xmaxf = floor (xmax); + xmaxc = ceil (xmax); + ymin = floor (MIN(y0, y1)); + ymax = MAX(y0, y1); + ymaxf = floor (ymax); + ymaxc = ceil (ymax); + xt0 = (xmin >> ART_UTILE_SHIFT) - uta->x0; + yt0 = (ymin >> ART_UTILE_SHIFT) - uta->y0; + xt1 = (xmaxf >> ART_UTILE_SHIFT) - uta->x0; + yt1 = (ymaxf >> ART_UTILE_SHIFT) - uta->y0; + if (xt0 == xt1 && yt0 == yt1) + { + /* entirely inside a microtile, this is easy! */ + xf0 = xmin & (ART_UTILE_SIZE - 1); + yf0 = ymin & (ART_UTILE_SIZE - 1); + xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf; + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + + ix = yt0 * uta->width + xt0; + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + else + { + double dx, dy; + int sx, sy; + + dx = x1 - x0; + dy = y1 - y0; + sx = dx > 0 ? 1 : dx < 0 ? -1 : 0; + sy = dy > 0 ? 1 : dy < 0 ? -1 : 0; + if (ymin == ymaxf) + { + /* special case horizontal (dx/dy slope would be infinite) */ + xf0 = xmin & (ART_UTILE_SIZE - 1); + yf0 = ymin & (ART_UTILE_SIZE - 1); + xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf; + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + + ix = yt0 * uta->width + xt0; + ix1 = yt0 * uta->width + xt1; + while (ix != ix1) + { + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, ART_UTILE_SIZE, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + ART_UTILE_SIZE, + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + xf0 = 0; + ix++; + } + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(0, + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + else + { + /* Do a Bresenham-style traversal of the line */ + double dx_dy; + double x, y; + double xn, yn; + + /* normalize coordinates to uta origin */ + x0 -= uta->x0 << ART_UTILE_SHIFT; + y0 -= uta->y0 << ART_UTILE_SHIFT; + x1 -= uta->x0 << ART_UTILE_SHIFT; + y1 -= uta->y0 << ART_UTILE_SHIFT; + if (dy < 0) + { + double tmp; + + tmp = x0; + x0 = x1; + x1 = tmp; + + tmp = y0; + y0 = y1; + y1 = tmp; + + dx = -dx; + sx = -sx; + dy = -dy; + /* we leave sy alone, because it would always be 1, + and we need it for the rbuf stuff. */ + } + xt0 = ((int)floor (x0) >> ART_UTILE_SHIFT); + xt1 = ((int)floor (x1) >> ART_UTILE_SHIFT); + /* now [xy]0 is above [xy]1 */ + + ix = yt0 * uta->width + xt0; + ix1 = yt1 * uta->width + xt1; +#ifdef VERBOSE + printf ("%% ix = %d,%d; ix1 = %d,%d\n", xt0, yt0, xt1, yt1); +#endif + + dx_dy = dx / dy; + x = x0; + y = y0; + while (ix != ix1) + { + int dix; + + /* figure out whether next crossing is horizontal or vertical */ +#ifdef VERBOSE + printf ("%% %d,%d\n", xt0, yt0); +#endif + yn = (yt0 + 1) << ART_UTILE_SHIFT; + + /* xn is the intercept with bottom edge of this tile. The + following expression is careful to result in exactly + x1 when yn = y1. */ + xn = x1 + dx_dy * (yn - y1); + + if (xt0 != (int)floor (xn) >> ART_UTILE_SHIFT) + { + /* horizontal crossing */ + xt0 += sx; + dix = sx; + if (dx > 0) + { + xn = xt0 << ART_UTILE_SHIFT; + yn = y0 + (xn - x0) / dx_dy; + + xf0 = (int)floor (x) & (ART_UTILE_SIZE - 1); + xf1 = ART_UTILE_SIZE; + } + else + { + xn = (xt0 + 1) << ART_UTILE_SHIFT; + yn = y0 + (xn - x0) / dx_dy; + + xf0 = 0; + xmaxc = (int)ceil (x); + xf1 = xmaxc - ((xt0 + 1) << ART_UTILE_SHIFT); + } + ymaxf = (int)floor (yn); + ymaxc = (int)ceil (yn); + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + } + else + { + /* vertical crossing */ + dix = uta->width; + xf0 = (int)floor (MIN(x, xn)) & (ART_UTILE_SIZE - 1); + xmax = MAX(x, xn); + xmaxc = (int)ceil (xmax); + xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT); + yf1 = ART_UTILE_SIZE; + + if (rbuf != NULL) + rbuf[yt0 * rbuf_rowstride + xt0] += sy; + + yt0++; + } + yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1); + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + + x = xn; + y = yn; + ix += dix; + } + xmax = MAX(x, x1); + xmaxc = ceil (xmax); + ymaxc = ceil (y1); + xf0 = (int)floor (MIN(x1, x)) & (ART_UTILE_SIZE - 1); + yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1); + xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT); + yf1 = ymaxc - (yt0 << ART_UTILE_SHIFT); + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + } +} + +/** + * art_uta_from_vpath: Generate uta covering a vpath. + * @vec: The source vpath. + * + * Generates a uta covering @vec. The resulting uta is of course + * approximate, ie it may cover more pixels than covered by @vec. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_vpath (const ArtVpath *vec) +{ + ArtUta *uta; + ArtIRect bbox; + int *rbuf; + int i; + double x, y; + int sum; + int xt, yt; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int width; + int height; + int ix; + + art_vpath_bbox_irect (vec, &bbox); + + uta = art_uta_new_coords (bbox.x0, bbox.y0, bbox.x1, bbox.y1); + + width = uta->width; + height = uta->height; + utiles = uta->utiles; + + rbuf = art_new (int, width * height); + for (i = 0; i < width * height; i++) + rbuf[i] = 0; + + x = 0; + y = 0; + for (i = 0; vec[i].code != ART_END; i++) + { + switch (vec[i].code) + { + case ART_MOVETO: + x = vec[i].x; + y = vec[i].y; + break; + case ART_LINETO: + art_uta_add_line (uta, vec[i].x, vec[i].y, x, y, rbuf, width); + x = vec[i].x; + y = vec[i].y; + break; + default: + /* this shouldn't happen */ + art_free (rbuf); + art_free (uta); + return NULL; + } + } + + /* now add in the filling from rbuf */ + ix = 0; + for (yt = 0; yt < height; yt++) + { + sum = 0; + for (xt = 0; xt < width; xt++) + { + sum += rbuf[ix]; + /* Nonzero winding rule - others are possible, but hardly + worth it. */ + if (sum != 0) + { + bb = utiles[ix]; + bb &= 0xffff0000; + bb |= (ART_UTILE_SIZE << 8) | ART_UTILE_SIZE; + utiles[ix] = bb; + if (xt != width - 1) + { + bb = utiles[ix + 1]; + bb &= 0xffff00; + bb |= ART_UTILE_SIZE; + utiles[ix + 1] = bb; + } + if (yt != height - 1) + { + bb = utiles[ix + width]; + bb &= 0xff0000ff; + bb |= ART_UTILE_SIZE << 8; + utiles[ix + width] = bb; + if (xt != width - 1) + { + utiles[ix + width + 1] &= 0xffff; + } + } + } + ix++; + } + } + + art_free (rbuf); + + return uta; +} diff --git a/libart_lgpl/art_uta_vpath.h b/libart_lgpl/art_uta_vpath.h new file mode 100644 index 0000000000..5a55876018 --- /dev/null +++ b/libart_lgpl/art_uta_vpath.h @@ -0,0 +1,45 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_UTA_VPATH_H__ +#define __ART_UTA_VPATH_H__ + +/* Basic data structures and constructors for microtile arrays */ + +#include <libart_lgpl/art_uta.h> +#include <libart_lgpl/art_vpath.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtUta * +art_uta_from_vpath (const ArtVpath *vec); + +/* This is a private function: */ +void +art_uta_add_line (ArtUta *uta, double x0, double y0, double x1, double y1, + int *rbuf, int rbuf_rowstride); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_UTA_VPATH_H__ */ + diff --git a/libart_lgpl/art_vpath.c b/libart_lgpl/art_vpath.c new file mode 100644 index 0000000000..fa7b903d11 --- /dev/null +++ b/libart_lgpl/art_vpath.c @@ -0,0 +1,241 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for vector paths */ + +#include "config.h" +#include "art_vpath.h" + +#include <math.h> +#include <stdlib.h> + +#include "art_misc.h" + +#include "art_rect.h" + +/** + * art_vpath_add_point: Add point to vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @code: The pathcode for the new point. + * @x: The X coordinate of the new point. + * @y: The Y coordinate of the new point. + * + * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath + * and *@pn_points_max as necessary. *@pn_points is incremented. + * + * This routine always adds the point after all points already in the + * vpath. Thus, it should be called in the order the points are + * desired. + **/ +void +art_vpath_add_point (ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y) +{ + int i; + + i = (*pn_points)++; + if (i == *pn_points_max) + art_expand (*p_vpath, ArtVpath, *pn_points_max); + (*p_vpath)[i].code = code; + (*p_vpath)[i].x = x; + (*p_vpath)[i].y = y; +} + +/* number of steps should really depend on radius. */ +#define CIRCLE_STEPS 128 + +/** + * art_vpath_new_circle: Create a new circle. + * @x: X coordinate of center. + * @y: Y coordinate of center. + * @r: radius. + * + * Creates a new polygon closely approximating a circle with center + * (@x, @y) and radius @r. Currently, the number of points used in the + * approximation is fixed, but that will probably change. + * + * Return value: The newly created #ArtVpath. + **/ +ArtVpath * +art_vpath_new_circle (double x, double y, double r) +{ + int i; + ArtVpath *vec; + double theta; + + vec = art_new (ArtVpath, CIRCLE_STEPS + 2); + + for (i = 0; i < CIRCLE_STEPS + 1; i++) + { + vec[i].code = i ? ART_LINETO : ART_MOVETO; + theta = (i & (CIRCLE_STEPS - 1)) * (M_PI * 2.0 / CIRCLE_STEPS); + vec[i].x = x + r * cos (theta); + vec[i].y = y - r * sin (theta); + } + vec[i].code = ART_END; + + return vec; +} + +/** + * art_vpath_affine_transform: Affine transform a vpath. + * @src: Source vpath to transform. + * @matrix: Affine transform. + * + * Computes the affine transform of the vpath, using @matrix as the + * transform. @matrix is stored in the same format as PostScript, ie. + * x' = @matrix[0] * x + @matrix[2] * y + @matrix[4] + * y' = @matrix[1] * x + @matrix[3] * y + @matrix[5] + * + * Return value: the newly allocated vpath resulting from the transform. +**/ +ArtVpath * +art_vpath_affine_transform (const ArtVpath *src, const double matrix[6]) +{ + int i; + int size; + ArtVpath *new; + double x, y; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtVpath, size + 1); + + for (i = 0; i < size; i++) + { + new[i].code = src[i].code; + x = src[i].x; + y = src[i].y; + new[i].x = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y = matrix[1] * x + matrix[3] * y + matrix[5]; + } + new[i].code = ART_END; + + return new; +} + +/** + * art_vpath_bbox_drect: Determine bounding box of vpath. + * @vec: Source vpath. + * @drect: Where to store bounding box. + * + * Determines bounding box of @vec, and stores it in @drect. + **/ +void +art_vpath_bbox_drect (const ArtVpath *vec, ArtDRect *drect) +{ + int i; + double x0, y0, x1, y1; + + if (vec[0].code == ART_END) + { + x0 = y0 = x1 = y1 = 0; + } + else + { + x0 = x1 = vec[0].x; + y0 = y1 = vec[0].y; + for (i = 1; vec[i].code != ART_END; i++) + { + if (vec[i].x < x0) x0 = vec[i].x; + if (vec[i].x > x1) x1 = vec[i].x; + if (vec[i].y < y0) y0 = vec[i].y; + if (vec[i].y > y1) y1 = vec[i].y; + } + } + drect->x0 = x0; + drect->y0 = y0; + drect->x1 = x1; + drect->y1 = y1; +} + +/** + * art_vpath_bbox_irect: Determine integer bounding box of vpath. + * @vec: Source vpath. + * idrect: Where to store bounding box. + * + * Determines integer bounding box of @vec, and stores it in @irect. + **/ +void +art_vpath_bbox_irect (const ArtVpath *vec, ArtIRect *irect) +{ + ArtDRect drect; + + art_vpath_bbox_drect (vec, &drect); + art_drect_to_irect (irect, &drect); +} + +#define PERTURBATION 2e-3 + +/** + * art_vpath_perturb: Perturb each point in vpath by small random amount. + * @src: Source vpath. + * + * Perturbs each of the points by a small random amount. This is + * helpful for cheating in cases when algorithms haven't attained + * numerical stability yet. + * + * Return value: Newly allocated vpath containing perturbed @src. + **/ +ArtVpath * +art_vpath_perturb (ArtVpath *src) +{ + int i; + int size; + ArtVpath *new; + double x, y; + double x_start, y_start; + int open; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtVpath, size + 1); + + x_start = 0; + y_start = 0; + open = 0; + for (i = 0; i < size; i++) + { + new[i].code = src[i].code; + x = src[i].x + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5; + y = src[i].y + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5; + if (src[i].code == ART_MOVETO) + { + x_start = x; + y_start = y; + open = 0; + } + else if (src[i].code == ART_MOVETO_OPEN) + open = 1; + if (!open && (i + 1 == size || src[i + 1].code != ART_LINETO)) + { + x = x_start; + y = y_start; + } + new[i].x = x; + new[i].y = y; + } + new[i].code = ART_END; + + return new; +} diff --git a/libart_lgpl/art_vpath.h b/libart_lgpl/art_vpath.h new file mode 100644 index 0000000000..4102d28908 --- /dev/null +++ b/libart_lgpl/art_vpath.h @@ -0,0 +1,66 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_VPATH_H__ +#define __ART_VPATH_H__ + +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_pathcode.h> + +/* Basic data structures and constructors for simple vector paths */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtVpath ArtVpath; + +/* CURVETO is not allowed! */ +struct _ArtVpath { + ArtPathcode code; + double x; + double y; +}; + +/* Some of the functions need to go into their own modules */ + +void +art_vpath_add_point (ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y); + +ArtVpath * +art_vpath_new_circle (double x, double y, double r); + +ArtVpath * +art_vpath_affine_transform (const ArtVpath *src, const double matrix[6]); + +void +art_vpath_bbox_drect (const ArtVpath *vec, ArtDRect *drect); + +void +art_vpath_bbox_irect (const ArtVpath *vec, ArtIRect *irect); + +ArtVpath * +art_vpath_perturb (ArtVpath *src); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_VPATH_H__ */ diff --git a/libart_lgpl/art_vpath_bpath.c b/libart_lgpl/art_vpath_bpath.c new file mode 100644 index 0000000000..3f9afe7229 --- /dev/null +++ b/libart_lgpl/art_vpath_bpath.c @@ -0,0 +1,328 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for bezier paths */ + +#include "config.h" +#include "art_vpath_bpath.h" + +#include <math.h> + +#include "art_misc.h" + +#include "art_bpath.h" +#include "art_vpath.h" + +/* p must be allocated 2^level points. */ + +/* level must be >= 1 */ +ArtPoint * +art_bezier_to_vec (double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + ArtPoint *p, + int level) +{ + double x_m, y_m; + +#ifdef VERBOSE + printf ("bezier_to_vec: %g,%g %g,%g %g,%g %g,%g %d\n", + x0, y0, x1, y1, x2, y2, x3, y3, level); +#endif + if (level == 1) { + x_m = (x0 + 3 * (x1 + x2) + x3) * 0.125; + y_m = (y0 + 3 * (y1 + y2) + y3) * 0.125; + p->x = x_m; + p->y = y_m; + p++; + p->x = x3; + p->y = y3; + p++; +#ifdef VERBOSE + printf ("-> (%g, %g) -> (%g, %g)\n", x_m, y_m, x3, y3); +#endif + } else { + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2, + xb1, yb1, xb2, yb2); +#endif + p = art_bezier_to_vec (x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, p, level - 1); + p = art_bezier_to_vec (x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, p, level - 1); + } + return p; +} + +#define RENDER_LEVEL 4 +#define RENDER_SIZE (1 << (RENDER_LEVEL)) + +/** + * art_vpath_render_bez: Render a bezier segment into the vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @x0: X coordinate of starting bezier point. + * @y0: Y coordinate of starting bezier point. + * @x1: X coordinate of first bezier control point. + * @y1: Y coordinate of first bezier control point. + * @x2: X coordinate of second bezier control point. + * @y2: Y coordinate of second bezier control point. + * @x3: X coordinate of ending bezier point. + * @y3: Y coordinate of ending bezier point. + * @flatness: Flatness control. + * + * Renders a bezier segment into the vector path, reallocating and + * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is + * incremented by the number of vector points added. + * + * This step includes (@x0, @y0) but not (@x3, @y3). + * + * The @flatness argument guides the amount of subdivision. The Adobe + * PostScript reference manual defines flatness as the maximum + * deviation between the any point on the vpath approximation and the + * corresponding point on the "true" curve, and we follow this + * definition here. A value of 0.25 should ensure high quality for aa + * rendering. +**/ +static void +art_vpath_render_bez (ArtVpath **p_vpath, int *pn, int *pn_max, + double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + double flatness) +{ + double x3_0, y3_0; + double z3_0_dot; + double z1_dot, z2_dot; + double z1_perp, z2_perp; + double max_perp_sq; + + double x_m, y_m; + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + /* It's possible to optimize this routine a fair amount. + + First, once the _dot conditions are met, they will also be met in + all further subdivisions. So we might recurse to a different + routine that only checks the _perp conditions. + + Second, the distance _should_ decrease according to fairly + predictable rules (a factor of 4 with each subdivision). So it might + be possible to note that the distance is within a factor of 4 of + acceptable, and subdivide once. But proving this might be hard. + + Third, at the last subdivision, x_m and y_m can be computed more + expeditiously (as in the routine above). + + Finally, if we were able to subdivide by, say 2 or 3, this would + allow considerably finer-grain control, i.e. fewer points for the + same flatness tolerance. This would speed things up downstream. + + In any case, this routine is unlikely to be the bottleneck. It's + just that I have this undying quest for more speed... + + */ + + x3_0 = x3 - x0; + y3_0 = y3 - y0; + + /* z3_0_dot is dist z0-z3 squared */ + z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; + + if (z3_0_dot < 0.001) + { + /* if start and end point are almost identical, the flatness tests + * don't work properly, so fall back on testing whether both of + * the other two control points are the same as the start point, + * too. + */ + if (hypot(x1 - x0, y1 - y0) < 0.001 + && hypot(x2 - x0, y2 - y0) < 0.001) + goto nosubdivide; + else + goto subdivide; + } + + /* we can avoid subdivision if: + + z1 has distance no more than flatness from the z0-z3 line + + z1 is no more z0'ward than flatness past z0-z3 + + z1 is more z0'ward than z3'ward on the line traversing z0-z3 + + and correspondingly for z2 */ + + /* perp is distance from line, multiplied by dist z0-z3 */ + max_perp_sq = flatness * flatness * z3_0_dot; + + z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0; + if (z1_perp * z1_perp > max_perp_sq) + goto subdivide; + + z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0; + if (z2_perp * z2_perp > max_perp_sq) + goto subdivide; + + z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0; + if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) + goto subdivide; + + z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0; + if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) + goto subdivide; + + if (z1_dot + z1_dot > z3_0_dot) + goto subdivide; + + if (z2_dot + z2_dot > z3_0_dot) + goto subdivide; + + + nosubdivide: + /* don't subdivide */ + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, x3, y3); + return; + + subdivide: + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2, + xb1, yb1, xb2, yb2); +#endif + art_vpath_render_bez (p_vpath, pn, pn_max, + x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness); + art_vpath_render_bez (p_vpath, pn, pn_max, + x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness); +} + +/** + * art_bez_path_to_vec: Create vpath from bezier path. + * @bez: Bezier path. + * @flatness: Flatness control. + * + * Creates a vector path closely approximating the bezier path defined by + * @bez. The @flatness argument controls the amount of subdivision. In + * general, the resulting vpath deviates by at most @flatness pixels + * from the "ideal" path described by @bez. + * + * Return value: Newly allocated vpath. + **/ +ArtVpath * +art_bez_path_to_vec (const ArtBpath *bez, double flatness) +{ + ArtVpath *vec; + int vec_n, vec_n_max; + int bez_index; + double x, y; + + vec_n = 0; + vec_n_max = RENDER_SIZE; + vec = art_new (ArtVpath, vec_n_max); + + /* Initialization is unnecessary because of the precondition that the + bezier path does not begin with LINETO or CURVETO, but is here + to make the code warning-free. */ + x = 0; + y = 0; + + bez_index = 0; + do + { +#ifdef VERBOSE + printf ("%s %g %g\n", + bez[bez_index].code == ART_CURVETO ? "curveto" : + bez[bez_index].code == ART_LINETO ? "lineto" : + bez[bez_index].code == ART_MOVETO ? "moveto" : + bez[bez_index].code == ART_MOVETO_OPEN ? "moveto-open" : + "end", bez[bez_index].x3, bez[bez_index].y3); +#endif + /* make sure space for at least one more code */ + if (vec_n >= vec_n_max) + art_expand (vec, ArtVpath, vec_n_max); + switch (bez[bez_index].code) + { + case ART_MOVETO_OPEN: + case ART_MOVETO: + case ART_LINETO: + x = bez[bez_index].x3; + y = bez[bez_index].y3; + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = x; + vec[vec_n].y = y; + vec_n++; + break; + case ART_END: + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = 0; + vec[vec_n].y = 0; + vec_n++; + break; + case ART_CURVETO: +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3); +#endif + art_vpath_render_bez (&vec, &vec_n, &vec_n_max, + x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3, + flatness); + x = bez[bez_index].x3; + y = bez[bez_index].y3; + break; + } + } + while (bez[bez_index++].code != ART_END); + return vec; +} + diff --git a/libart_lgpl/art_vpath_bpath.h b/libart_lgpl/art_vpath_bpath.h new file mode 100644 index 0000000000..b5ca7c1939 --- /dev/null +++ b/libart_lgpl/art_vpath_bpath.h @@ -0,0 +1,43 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_VPATH_BPATH_H__ +#define __ART_VPATH_BPATH_H__ + +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_vpath.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtPoint *art_bezier_to_vec (double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + ArtPoint *p, + int level); + +ArtVpath *art_bez_path_to_vec (const ArtBpath *bez, double flatness); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_VPATH_BPATH_H__ */ diff --git a/libart_lgpl/art_vpath_dash.c b/libart_lgpl/art_vpath_dash.c new file mode 100644 index 0000000000..3c98a96d9f --- /dev/null +++ b/libart_lgpl/art_vpath_dash.c @@ -0,0 +1,200 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Apply a dash style to a vector path. */ + +#include "config.h" +#include "art_vpath_dash.h" + +#include <math.h> +#include <stdlib.h> + +#include "art_misc.h" + +#include "art_vpath.h" + + +/* Return the length of the largest subpath within vpath */ +static int +art_vpath_dash_max_subpath (const ArtVpath *vpath) +{ + int max_subpath; + int i; + int start; + + max_subpath = 0; + start = 0; + for (i = 0; vpath[i].code != ART_END; i++) + { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) + { + if (i - start > max_subpath) + max_subpath = i - start; + start = i; + } + } + if (i - start > max_subpath) + max_subpath = i - start; + + return max_subpath; +} + +/** + * art_vpath_dash: Add dash style to vpath. + * @vpath: Original vpath. + * @dash: Dash style. + * + * Creates a new vpath that is the result of applying dash style @dash + * to @vpath. + * + * This implementation has two known flaws: + * + * First, it adds a spurious break at the beginning of the vpath. The + * only way I see to resolve this flaw is to run the state forward one + * dash break at the beginning, and fix up by looping back to the + * first dash break at the end. This is doable but of course adds some + * complexity. + * + * Second, it does not suppress output points that are within epsilon + * of each other. + * + * Return value: Newly created vpath. + **/ +ArtVpath * +art_vpath_dash (const ArtVpath *vpath, const ArtVpathDash *dash) +{ + int max_subpath; + double *dists; + ArtVpath *result; + int n_result, n_result_max; + int start, end; + int i; + double total_dist; + + /* state while traversing dasharray - offset is offset of current dash + value, toggle is 0 for "off" and 1 for "on", and phase is the distance + in, >= 0, < dash->dash[offset]. */ + int offset, toggle; + double phase; + + /* initial values */ + int offset_init, toggle_init; + double phase_init; + + max_subpath = art_vpath_dash_max_subpath (vpath); + dists = art_new (double, max_subpath); + + n_result = 0; + n_result_max = 16; + result = art_new (ArtVpath, n_result_max); + + /* determine initial values of dash state */ + toggle_init = 1; + offset_init = 0; + phase_init = dash->offset; + while (phase_init >= dash->dash[offset_init]) + { + toggle_init = !toggle_init; + phase_init -= dash->dash[offset_init]; + offset_init++; + if (offset_init == dash->n_dash) + offset_init = 0; + } + + for (start = 0; vpath[start].code != ART_END; start = end) + { + for (end = start + 1; vpath[end].code == ART_LINETO; end++); + /* subpath is [start..end) */ + total_dist = 0; + for (i = start; i < end - 1; i++) + { + double dx, dy; + + dx = vpath[i + 1].x - vpath[i].x; + dy = vpath[i + 1].y - vpath[i].y; + dists[i - start] = sqrt (dx * dx + dy * dy); + total_dist += dists[i - start]; + } + if (total_dist <= dash->dash[offset_init] - phase_init) + { + /* subpath fits entirely within first dash */ + if (toggle_init) + { + for (i = start; i < end; i++) + art_vpath_add_point (&result, &n_result, &n_result_max, + vpath[i].code, vpath[i].x, vpath[i].y); + } + } + else + { + /* subpath is composed of at least one dash - thus all + generated pieces are open */ + double dist; + + phase = phase_init; + offset = offset_init; + toggle = toggle_init; + dist = 0; + i = start; + if (toggle) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO_OPEN, vpath[i].x, vpath[i].y); + while (i != end - 1) + { + if (dists[i - start] - dist > dash->dash[offset] - phase) + { + /* dash boundary is next */ + double a; + double x, y; + + dist += dash->dash[offset] - phase; + a = dist / dists[i - start]; + x = vpath[i].x + a * (vpath[i + 1].x - vpath[i].x); + y = vpath[i].y + a * (vpath[i + 1].y - vpath[i].y); + art_vpath_add_point (&result, &n_result, &n_result_max, + toggle ? ART_LINETO : ART_MOVETO_OPEN, + x, y); + /* advance to next dash */ + toggle = !toggle; + phase = 0; + offset++; + if (offset == dash->n_dash) + offset = 0; + } + else + { + /* end of line in vpath is next */ + phase += dists[i - start] - dist; + i++; + dist = 0; + if (toggle) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, vpath[i].x, vpath[i].y); + } + } + } + } + + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_END, 0, 0); + + art_free (dists); + + return result; +} diff --git a/libart_lgpl/art_vpath_dash.h b/libart_lgpl/art_vpath_dash.h new file mode 100644 index 0000000000..1ee7b33349 --- /dev/null +++ b/libart_lgpl/art_vpath_dash.h @@ -0,0 +1,46 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_VPATH_DASH_H__ +#define __ART_VPATH_DASH_H__ + +/* Apply a dash style to a vector path. */ + +#include <libart_lgpl/art_vpath.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _ArtVpathDash ArtVpathDash; + +struct _ArtVpathDash { + double offset; + int n_dash; + double *dash; +}; + +ArtVpath * +art_vpath_dash (const ArtVpath *vpath, const ArtVpathDash *dash); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_VPATH_DASH_H__ */ diff --git a/libart_lgpl/art_vpath_svp.c b/libart_lgpl/art_vpath_svp.c new file mode 100644 index 0000000000..000265c376 --- /dev/null +++ b/libart_lgpl/art_vpath_svp.c @@ -0,0 +1,196 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* "Unsort" a sorted vector path into an ordinary vector path. */ + +#include "config.h" +#include "art_vpath_svp.h" + +#include <stdio.h> /* for printf - debugging */ +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" + +typedef struct _ArtVpathSVPEnd ArtVpathSVPEnd; + +struct _ArtVpathSVPEnd { + int seg_num; + int which; /* 0 = top, 1 = bottom */ + double x, y; +}; + +#define EPSILON 1e-6 + +static int +art_vpath_svp_point_compare (double x1, double y1, double x2, double y2) +{ + if (y1 - EPSILON > y2) return 1; + if (y1 + EPSILON < y2) return -1; + if (x1 - EPSILON > x2) return 1; + if (x1 + EPSILON < x2) return -1; + return 0; +} + +static int +art_vpath_svp_compare (const void *s1, const void *s2) +{ + const ArtVpathSVPEnd *e1 = s1; + const ArtVpathSVPEnd *e2 = s2; + + return art_vpath_svp_point_compare (e1->x, e1->y, e2->x, e2->y); +} + +/* Convert from sorted vector path representation into regular + vector path representation. + + Status of this routine: + + Basic correctness: Only works with closed paths. + + Numerical stability: Not known to work when more than two segments + meet at a point. + + Speed: Should be pretty good. + + Precision: Does not degrade precision. + +*/ +/** + * art_vpath_from_svp: Convert from svp to vpath form. + * @svp: Original #ArtSVP. + * + * Converts the sorted vector path @svp into standard vpath form. + * + * Return value: the newly allocated vpath. + **/ +ArtVpath * +art_vpath_from_svp (const ArtSVP *svp) +{ + int n_segs = svp->n_segs; + ArtVpathSVPEnd *ends; + ArtVpath *new; + int *visited; + int n_new, n_new_max; + int i, k; + int j = 0; /* Quiet compiler */ + int seg_num; + int first; + double last_x, last_y; + int n_points; + int pt_num; + + last_x = 0; /* to eliminate "uninitialized" warning */ + last_y = 0; + + ends = art_new (ArtVpathSVPEnd, n_segs * 2); + for (i = 0; i < svp->n_segs; i++) + { + int lastpt; + + ends[i * 2].seg_num = i; + ends[i * 2].which = 0; + ends[i * 2].x = svp->segs[i].points[0].x; + ends[i * 2].y = svp->segs[i].points[0].y; + + lastpt = svp->segs[i].n_points - 1; + ends[i * 2 + 1].seg_num = i; + ends[i * 2 + 1].which = 1; + ends[i * 2 + 1].x = svp->segs[i].points[lastpt].x; + ends[i * 2 + 1].y = svp->segs[i].points[lastpt].y; + } + qsort (ends, n_segs * 2, sizeof (ArtVpathSVPEnd), art_vpath_svp_compare); + + n_new = 0; + n_new_max = 16; /* I suppose we _could_ estimate this from traversing + the svp, so we don't have to reallocate */ + new = art_new (ArtVpath, n_new_max); + + visited = art_new (int, n_segs); + for (i = 0; i < n_segs; i++) + visited[i] = 0; + + first = 1; + for (i = 0; i < n_segs; i++) + { + if (!first) + { + /* search for the continuation of the existing subpath */ + /* This could be a binary search (which is why we sorted, above) */ + for (j = 0; j < n_segs * 2; j++) + { + if (!visited[ends[j].seg_num] && + art_vpath_svp_point_compare (last_x, last_y, + ends[j].x, ends[j].y) == 0) + break; + } + if (j == n_segs * 2) + first = 1; + } + if (first) + { + /* start a new subpath */ + for (j = 0; j < n_segs * 2; j++) + if (!visited[ends[j].seg_num]) + break; + } + if (j == n_segs * 2) + { + printf ("failure\n"); + } + seg_num = ends[j].seg_num; + n_points = svp->segs[seg_num].n_points; + for (k = 0; k < n_points; k++) + { + pt_num = svp->segs[seg_num].dir ? k : n_points - (1 + k); + if (k == 0) + { + if (first) + { + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_MOVETO, + svp->segs[seg_num].points[pt_num].x, + svp->segs[seg_num].points[pt_num].y); + } + } + else + { + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_LINETO, + svp->segs[seg_num].points[pt_num].x, + svp->segs[seg_num].points[pt_num].y); + if (k == n_points - 1) + { + last_x = svp->segs[seg_num].points[pt_num].x; + last_y = svp->segs[seg_num].points[pt_num].y; + /* to make more robust, check for meeting first_[xy], + set first if so */ + } + } + first = 0; + } + visited[seg_num] = 1; + } + + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_END, 0, 0); + art_free (visited); + art_free (ends); + return new; +} diff --git a/libart_lgpl/art_vpath_svp.h b/libart_lgpl/art_vpath_svp.h new file mode 100644 index 0000000000..2df9641f58 --- /dev/null +++ b/libart_lgpl/art_vpath_svp.h @@ -0,0 +1,38 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __ART_VPATH_SVP_H__ +#define __ART_VPATH_SVP_H__ + +/* "Unsort" a sorted vector path into an ordinary vector path. */ + +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +ArtVpath *art_vpath_from_svp (const ArtSVP *svp); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ART_VPATH_SVP_H__ */ diff --git a/libart_lgpl/libart.h b/libart_lgpl/libart.h new file mode 100644 index 0000000000..4a9eeea60a --- /dev/null +++ b/libart_lgpl/libart.h @@ -0,0 +1,39 @@ +#ifndef LIBART_H +#define LIBART_H 1 + +#include <libart_lgpl/art_affine.h> +#include <libart_lgpl/art_alphagamma.h> +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_filterlevel.h> +#include <libart_lgpl/art_gray_svp.h> +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_pathcode.h> +#include <libart_lgpl/art_pixbuf.h> +#include <libart_lgpl/art_point.h> +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_rect_svp.h> +#include <libart_lgpl/art_rect_uta.h> +#include <libart_lgpl/art_rgb.h> +#include <libart_lgpl/art_rgb_affine.h> +#include <libart_lgpl/art_rgb_bitmap_affine.h> +#include <libart_lgpl/art_rgb_pixbuf_affine.h> +#include <libart_lgpl/art_rgb_rgba_affine.h> +#include <libart_lgpl/art_rgb_svp.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_ops.h> +#include <libart_lgpl/art_svp_point.h> +#include <libart_lgpl/art_svp_render_aa.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> +#include <libart_lgpl/art_svp_wind.h> +#include <libart_lgpl/art_uta.h> +#include <libart_lgpl/art_uta_ops.h> +#include <libart_lgpl/art_uta_rect.h> +#include <libart_lgpl/art_uta_svp.h> +#include <libart_lgpl/art_uta_vpath.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_vpath_bpath.h> +#include <libart_lgpl/art_vpath_dash.h> +#include <libart_lgpl/art_vpath_svp.h> + +#endif diff --git a/libart_lgpl/libart.m4 b/libart_lgpl/libart.m4 new file mode 100644 index 0000000000..9380a222de --- /dev/null +++ b/libart_lgpl/libart.m4 @@ -0,0 +1,165 @@ +# Configure paths for LIBART +# Raph Levien 98-11-18 +# stolen from Manish Singh 98-9-30 +# stolen back from Frank Belew +# stolen from Manish Singh +# Shamelessly stolen from Owen Taylor + +dnl AM_PATH_LIBART([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for LIBART, and define LIBART_CFLAGS and LIBART_LIBS +dnl +AC_DEFUN(AM_PATH_LIBART, +[dnl +dnl Get the cflags and libraries from the libart-config script +dnl +AC_ARG_WITH(libart-prefix,[ --with-libart-prefix=PFX Prefix where LIBART is installed (optional)], + libart_prefix="$withval", libart_prefix="") +AC_ARG_WITH(libart-exec-prefix,[ --with-libart-exec-prefix=PFX Exec prefix where LIBART is installed (optional)], + libart_exec_prefix="$withval", libart_exec_prefix="") +AC_ARG_ENABLE(libarttest, [ --disable-libarttest Do not try to compile and run a test LIBART program], + , enable_libarttest=yes) + + if test x$libart_exec_prefix != x ; then + libart_args="$libart_args --exec-prefix=$libart_exec_prefix" + if test x${LIBART_CONFIG+set} != xset ; then + LIBART_CONFIG=$libart_exec_prefix/bin/libart-config + fi + fi + if test x$libart_prefix != x ; then + libart_args="$libart_args --prefix=$libart_prefix" + if test x${LIBART_CONFIG+set} != xset ; then + LIBART_CONFIG=$libart_prefix/bin/libart-config + fi + fi + + AC_PATH_PROG(LIBART_CONFIG, libart-config, no) + min_libart_version=ifelse([$1], ,0.2.5,$1) + AC_MSG_CHECKING(for LIBART - version >= $min_libart_version) + no_libart="" + if test "$LIBART_CONFIG" = "no" ; then + no_libart=yes + else + LIBART_CFLAGS=`$LIBART_CONFIG $libartconf_args --cflags` + LIBART_LIBS=`$LIBART_CONFIG $libartconf_args --libs` + + libart_major_version=`$LIBART_CONFIG $libart_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + libart_minor_version=`$LIBART_CONFIG $libart_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + libart_micro_version=`$LIBART_CONFIG $libart_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + if test "x$enable_libarttest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $LIBART_CFLAGS" + LIBS="$LIBS $LIBART_LIBS" +dnl +dnl Now check if the installed LIBART is sufficiently new. (Also sanity +dnl checks the results of libart-config to some extent +dnl + rm -f conf.libarttest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libart_lgpl/libart.h> + +char* +my_strdup (char *str) +{ + char *new_str; + + if (str) + { + new_str = malloc ((strlen (str) + 1) * sizeof(char)); + strcpy (new_str, str); + } + else + new_str = NULL; + + return new_str; +} + +int main () +{ + int major, minor, micro; + char *tmp_version; + + system ("touch conf.libarttest"); + + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = my_strdup("$min_libart_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string\n", "$min_libart_version"); + exit(1); + } + + if (($libart_major_version > major) || + (($libart_major_version == major) && ($libart_minor_version > minor)) || + (($libart_major_version == major) && ($libart_minor_version == minor) && ($libart_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** 'libart-config --version' returned %d.%d.%d, but the minimum version\n", $libart_major_version, $libart_minor_version, $libart_micro_version); + printf("*** of LIBART required is %d.%d.%d. If libart-config is correct, then it is\n", major, minor, micro); + printf("*** best to upgrade to the required version.\n"); + printf("*** If libart-config was wrong, set the environment variable LIBART_CONFIG\n"); + printf("*** to point to the correct copy of libart-config, and remove the file\n"); + printf("*** config.cache before re-running configure\n"); + return 1; + } +} + +],, no_libart=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + if test "x$no_libart" = x ; then + AC_MSG_RESULT(yes) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test "$LIBART_CONFIG" = "no" ; then + echo "*** The libart-config script installed by LIBART could not be found" + echo "*** If LIBART was installed in PREFIX, make sure PREFIX/bin is in" + echo "*** your path, or set the LIBART_CONFIG environment variable to the" + echo "*** full path to libart-config." + else + if test -f conf.libarttest ; then + : + else + echo "*** Could not run LIBART test program, checking why..." + CFLAGS="$CFLAGS $LIBART_CFLAGS" + LIBS="$LIBS $LIBART_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <libart_lgpl/libart.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding LIBART or finding the wrong" + echo "*** version of LIBART. If it is not finding LIBART, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means LIBART was incorrectly installed" + echo "*** or that you have moved LIBART since it was installed. In the latter case, you" + echo "*** may want to edit the libart-config script: $LIBART_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + fi + LIBART_CFLAGS="" + LIBART_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(LIBART_CFLAGS) + AC_SUBST(LIBART_LIBS) + rm -f conf.libarttest +]) diff --git a/libgnomecanvas/Makefile.am b/libgnomecanvas/Makefile.am new file mode 100644 index 0000000000..46fdcc81ef --- /dev/null +++ b/libgnomecanvas/Makefile.am @@ -0,0 +1,83 @@ +privsolib_LTLIBRARIES = libgnomecanvas-2.la + +libgnomecanvas_2_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -I$(top_srcdir) \ + $(GNOME_PLATFORM_CFLAGS) \ + -DGNOMECANVASLIBDIR=\""$(libdir)"\" \ + -DGNOMECANVASDATADIR=\""$(datadir)"\" \ + -DGNOMECANVASPIXMAPDIR=\""$(datadir)/pixmaps"\" \ + -DGNOMECANVASBINDIR=\""$(bindir)"\" \ + -DGNOMECANVASLOCALSTATEDIR=\""$(localstatedir)"\" \ + -DG_LOG_DOMAIN=\"GnomeCanvas\" + +libgnomecanvas_2_la_LIBADD = \ + $(top_builddir)/libart_lgpl/libart_lgpl_2.la \ + $(GNOME_PLATFORM_LIBS) + +libgnomecanvas_2_la_LDFLAGS = $(NO_UNDEFINED) + +libgnomecanvas_2_la_SOURCES = \ + gailcanvas.c \ + gailcanvas.h \ + gailcanvasgroup.c \ + gailcanvasgroup.h \ + gailcanvasgroupfactory.c \ + gailcanvasgroupfactory.h \ + gailcanvasitem.c \ + gailcanvasitem.h \ + gailcanvasitemfactory.c \ + gailcanvasitemfactory.h \ + gailcanvastext.c \ + gailcanvastext.h \ + gailcanvastextfactory.c \ + gailcanvastextfactory.h \ + gailcanvaswidget.c \ + gailcanvaswidget.h \ + gailcanvaswidgetfactory.c \ + gailcanvaswidgetfactory.h \ + gnome-canvas-bpath.c \ + gnome-canvas-bpath.h \ + gnome-canvas-clipgroup.c \ + gnome-canvas-clipgroup.h \ + gnome-canvas-i18n.h \ + gnome-canvas-line.c \ + gnome-canvas-line.h \ + gnome-canvas-path-def.c \ + gnome-canvas-path-def.h \ + gnome-canvas-pixbuf.c \ + gnome-canvas-pixbuf.h \ + gnome-canvas-polygon.c \ + gnome-canvas-polygon.h \ + gnome-canvas-rect-ellipse.c \ + gnome-canvas-rect-ellipse.h \ + gnome-canvas-rich-text.c \ + gnome-canvas-rich-text.h \ + gnome-canvas-shape-private.h \ + gnome-canvas-shape.c \ + gnome-canvas-shape.h \ + gnome-canvas-text.c \ + gnome-canvas-text.h \ + gnome-canvas-util.c \ + gnome-canvas-util.h \ + gnome-canvas-widget.c \ + gnome-canvas-widget.h \ + gnome-canvas.c \ + gnome-canvas.h \ + libgnomecanvas.h \ + libgnomecanvastypes.c + +MARSHAL_GENERATED = \ + gnome-canvas-marshal.c \ + gnome-canvas-marshal.h + +@EVO_MARSHAL_RULE@ + +BUILT_SOURCES = $(MARSHAL_GENERATED) + +CLEANFILES = $(BUILT_SOURCES) + +EXTRA_DIST = \ + gnome-canvas-marshal.list + +-include $(top_srcdir)/git.mk diff --git a/libgnomecanvas/gailcanvas.c b/libgnomecanvas/gailcanvas.c new file mode 100644 index 0000000000..4ec0b67de3 --- /dev/null +++ b/libgnomecanvas/gailcanvas.c @@ -0,0 +1,266 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001, 2002, 2003 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-text.h> +#include <libgnomecanvas/gnome-canvas-rich-text.h> +#include <libgnomecanvas/gnome-canvas-widget.h> +#include "gailcanvas.h" +#include "gailcanvasitem.h" +#include "gailcanvasgroupfactory.h" +#include "gailcanvastextfactory.h" +#include "gailcanvasitemfactory.h" +#include "gailcanvaswidgetfactory.h" + +static void gail_canvas_class_init (GailCanvasClass *klass); +static void gail_canvas_real_initialize (AtkObject *obj, + gpointer data); + +static gint gail_canvas_get_n_children (AtkObject *obj); +static AtkObject* gail_canvas_ref_child (AtkObject *obj, + gint i); + +static void adjustment_changed (GtkAdjustment *adjustment, + GnomeCanvas *canvas); + +static AtkObject* gail_canvas_factory_create_accessible (GObject *obj); + +static GType gail_canvas_factory_get_accessible_type (void); + +G_DEFINE_TYPE (GailCanvasFactory, + gail_canvas_factory, + ATK_TYPE_OBJECT_FACTORY); + +static void +gail_canvas_factory_init (GailCanvasFactory *foo) +{ + ; +} + +static void +gail_canvas_factory_class_init (GailCanvasFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = gail_canvas_factory_create_accessible; + class->get_accessible_type = gail_canvas_factory_get_accessible_type; +} + +static AtkObject* +gail_canvas_factory_create_accessible (GObject *obj) +{ + return gail_canvas_new (GTK_WIDGET (obj)); +} + +static GType +gail_canvas_factory_get_accessible_type (void) +{ + return GAIL_TYPE_CANVAS; +} + +GType +gail_canvas_get_type (void) +{ + static GType type = 0; + + if (!type) + { + GType parent_type = g_type_parent (GNOME_TYPE_CANVAS); + AtkObjectFactory *factory = atk_registry_get_factory ( + atk_get_default_registry (), + parent_type); + GType atkobject_parent_type = atk_object_factory_get_accessible_type (factory); + GTypeQuery query; + static GTypeInfo tinfo = + { + 0, /* class size */ + (GBaseInitFunc) NULL, /* base init */ + (GBaseFinalizeFunc) NULL, /* base finalize */ + (GClassInitFunc) gail_canvas_class_init, /* class init */ + (GClassFinalizeFunc) NULL, /* class finalize */ + NULL, /* class data */ + 0, /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) NULL, /* instance init */ + NULL /* value table */ + }; + g_type_query (atkobject_parent_type, &query); + tinfo.class_size = query.class_size; + tinfo.instance_size = query.instance_size; + + /* use the size obtained from the parent type factory */ + type = g_type_register_static (atkobject_parent_type, + "GailCanvas", &tinfo, 0); + } + + return type; +} + +static AtkObjectClass *parent_atk_object_class; + +/** + * Tell ATK how to create the appropriate AtkObject peers + **/ +void +gail_canvas_init (void) +{ + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS, + gail_canvas_factory_get_type ()); + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS_GROUP, + gail_canvas_group_factory_get_type ()); + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS_TEXT, + gail_canvas_text_factory_get_type ()); + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS_RICH_TEXT, + gail_canvas_text_factory_get_type ()); + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS_WIDGET, + gail_canvas_widget_factory_get_type()); + atk_registry_set_factory_type (atk_get_default_registry (), + GNOME_TYPE_CANVAS_ITEM, + gail_canvas_item_factory_get_type ()); +} + +static void +gail_canvas_class_init (GailCanvasClass *klass) +{ + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + parent_atk_object_class = g_type_class_peek_parent (klass); + + class->get_n_children = gail_canvas_get_n_children; + class->ref_child = gail_canvas_ref_child; + class->initialize = gail_canvas_real_initialize; +} + +AtkObject* +gail_canvas_new (GtkWidget *widget) +{ + GObject *object; + AtkObject *accessible; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), NULL); + + object = g_object_new (GAIL_TYPE_CANVAS, NULL); + + accessible = ATK_OBJECT (object); + atk_object_initialize (accessible, widget); + + return accessible; +} + + +static void +gail_canvas_real_initialize (AtkObject *obj, + gpointer data) +{ + GnomeCanvas *canvas; + GtkAdjustment *adj; + + parent_atk_object_class->initialize (obj, data); + + canvas = GNOME_CANVAS (data); + + adj = canvas->layout.hadjustment; + g_signal_connect (adj, + "value_changed", + G_CALLBACK (adjustment_changed), + canvas); + + adj = canvas->layout.vadjustment; + g_signal_connect (adj, + "value_changed", + G_CALLBACK (adjustment_changed), + canvas); + + obj->role = ATK_ROLE_LAYERED_PANE; +} + +static gint +gail_canvas_get_n_children (AtkObject* obj) +{ + GtkAccessible *accessible; + GtkWidget *widget; + GnomeCanvas *canvas; + GnomeCanvasGroup *root_group; + + g_return_val_if_fail (GAIL_IS_CANVAS (obj), 0); + + accessible = GTK_ACCESSIBLE (obj); + widget = accessible->widget; + if (widget == NULL) + /* State is defunct */ + return 0; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), 0); + + canvas = GNOME_CANVAS (widget); + root_group = gnome_canvas_root (canvas); + g_return_val_if_fail (root_group, 0); + return 1; +} + +static AtkObject* +gail_canvas_ref_child (AtkObject *obj, + gint i) +{ + GtkAccessible *accessible; + GtkWidget *widget; + GnomeCanvas *canvas; + GnomeCanvasGroup *root_group; + AtkObject *atk_object; + + /* Canvas only has one child, so return NULL if anything else is requested */ + if (i != 0) + return NULL; + g_return_val_if_fail (GAIL_IS_CANVAS (obj), NULL); + + accessible = GTK_ACCESSIBLE (obj); + widget = accessible->widget; + if (widget == NULL) + /* State is defunct */ + return NULL; + g_return_val_if_fail (GNOME_IS_CANVAS (widget), NULL); + + canvas = GNOME_CANVAS (widget); + root_group = gnome_canvas_root (canvas); + g_return_val_if_fail (root_group, NULL); + + atk_object = atk_gobject_accessible_for_object (G_OBJECT (root_group)); + g_object_ref (atk_object); + return atk_object; +} + +static void +adjustment_changed (GtkAdjustment *adjustment, + GnomeCanvas *canvas) +{ + AtkObject *atk_obj; + + /* + * The scrollbars have changed + */ + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (canvas)); + + g_signal_emit_by_name (atk_obj, "visible_data_changed"); +} + diff --git a/libgnomecanvas/gailcanvas.h b/libgnomecanvas/gailcanvas.h new file mode 100644 index 0000000000..46cd7b7187 --- /dev/null +++ b/libgnomecanvas/gailcanvas.h @@ -0,0 +1,74 @@ +/* gailcanvas.h - code from GAIL, the + * Gnome Accessibility Implementation Library + * Copyright 2001-2006 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_H__ +#define __GAIL_CANVAS_H__ + +#include <gtk/gtk.h> + +/* This code provides the ATK implementation for gnome-canvas widgets. */ + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS (gail_canvas_get_type ()) +#define GAIL_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS, GailCanvas)) +#define GAIL_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS, GailCanvasClass)) +#define GAIL_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS)) +#define GAIL_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS)) +#define GAIL_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS, GailCanvasClass)) + +#define GAIL_TYPE_CANVAS_FACTORY (gail_canvas_factory_get_type ()) +#define GAIL_CANVAS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_FACTORY, GailCanvasFactory)) +#define GAIL_CANVAS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_FACTORY, GailCanvasFactoryClass)) +#define GAIL_IS_CANVAS_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_FACTORY)) +#define GAIL_IS_CANVAS_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_FACTORY)) +#define GAIL_CANVAS_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_FACTORY, GailCanvasFactoryClass)) + +typedef struct _GailCanvas GailCanvas; +typedef struct _GailCanvasClass GailCanvasClass; +typedef struct _GailCanvasFactory GailCanvasFactory; +typedef struct _GailCanvasFactoryClass GailCanvasFactoryClass; + +struct _GailCanvasFactory +{ + AtkObjectFactory parent; +}; + +struct _GailCanvasFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + + +GType gail_canvas_get_type (void); + +struct _GailCanvas; + +struct _GailCanvasClass; + +AtkObject* gail_canvas_new (GtkWidget *widget); + +void gail_canvas_init (void); + +GType gail_canvas_factory_get_type(void); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_H__ */ diff --git a/libgnomecanvas/gailcanvasgroup.c b/libgnomecanvas/gailcanvasgroup.c new file mode 100644 index 0000000000..1750f679c2 --- /dev/null +++ b/libgnomecanvas/gailcanvasgroup.c @@ -0,0 +1,102 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include <libgnomecanvas/gnome-canvas.h> +#include "gailcanvasgroup.h" + +static gint gail_canvas_group_get_n_children (AtkObject *obj); +static AtkObject* gail_canvas_group_ref_child (AtkObject *obj, + gint i); + +G_DEFINE_TYPE(GailCanvasGroup, + gail_canvas_group, + GAIL_TYPE_CANVAS_ITEM); + +static void +gail_canvas_group_init (GailCanvasGroup *foo) +{ + ; +} + +AtkObject* +gail_canvas_group_new (GObject *obj) +{ + gpointer object; + AtkObject *atk_object; + + g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (obj), NULL); + object = g_object_new (GAIL_TYPE_CANVAS_GROUP, NULL); + atk_object = ATK_OBJECT (object); + atk_object_initialize (atk_object, obj); + atk_object->role = ATK_ROLE_PANEL; + return atk_object; +} + +static void +gail_canvas_group_class_init (GailCanvasGroupClass *klass) +{ + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + + class->get_n_children = gail_canvas_group_get_n_children; + class->ref_child = gail_canvas_group_ref_child; +} + +static gint +gail_canvas_group_get_n_children (AtkObject *obj) +{ + AtkGObjectAccessible *atk_gobject; + GnomeCanvasGroup *group; + GObject *g_obj; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (obj), 0); + atk_gobject = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobject); + g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (g_obj), 0); + group = GNOME_CANVAS_GROUP (g_obj); + return g_list_length (group->item_list); +} + + +static AtkObject * +gail_canvas_group_ref_child (AtkObject *obj, + gint i) +{ + AtkGObjectAccessible *atk_gobject; + GnomeCanvasGroup *group; + GnomeCanvasItem *item; + AtkObject *accessible; + GObject *g_obj; + GList *list_item; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (obj), NULL); + atk_gobject = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobject); + g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (g_obj), NULL); + group = GNOME_CANVAS_GROUP (g_obj); + + list_item = g_list_nth (group->item_list, i); + if (!list_item) + return NULL; + g_return_val_if_fail (list_item->data, NULL); + item = GNOME_CANVAS_ITEM (list_item->data); + accessible = atk_gobject_accessible_for_object (G_OBJECT (item)); + g_object_ref (accessible); + return accessible; +} diff --git a/libgnomecanvas/gailcanvasgroup.h b/libgnomecanvas/gailcanvasgroup.h new file mode 100644 index 0000000000..4f50b9da26 --- /dev/null +++ b/libgnomecanvas/gailcanvasgroup.h @@ -0,0 +1,55 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_GROUP_H__ +#define __GAIL_CANVAS_GROUP_H__ + +#include <libgnomecanvas/gnome-canvas.h> +#include <atk/atk.h> +#include "gailcanvasitem.h" + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_GROUP (gail_canvas_group_get_type ()) +#define GAIL_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_GROUP, GailCanvasGroup)) +#define GAIL_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_GROUP, GailCanvasGroupClass)) +#define GAIL_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_GROUP)) +#define GAIL_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_GROUP)) +#define GAIL_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_GROUP, GailCanvasGroupClass)) + +typedef struct _GailCanvasGroup GailCanvasGroup; +typedef struct _GailCanvasGroupClass GailCanvasGroupClass; + +struct _GailCanvasGroup +{ + GailCanvasItem parent; +}; + +GType gail_canvas_group_get_type (void); + +struct _GailCanvasGroupClass +{ + GailCanvasItemClass parent_class; +}; + +AtkObject* gail_canvas_group_new (GObject *obj); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_GROUP_H__ */ diff --git a/libgnomecanvas/gailcanvasgroupfactory.c b/libgnomecanvas/gailcanvasgroupfactory.c new file mode 100644 index 0000000000..10a7d2e056 --- /dev/null +++ b/libgnomecanvas/gailcanvasgroupfactory.c @@ -0,0 +1,77 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include "gailcanvasgroupfactory.h" +#include "gailcanvasgroup.h" + +static void gail_canvas_group_factory_class_init (GailCanvasGroupFactoryClass *klass); + +static AtkObject * gail_canvas_group_factory_create_accessible (GObject *obj); + +static GType gail_canvas_group_factory_get_accessible_type (void); + +GType +gail_canvas_group_factory_get_type (void) +{ + static GType type = 0; + + if (!type) + { + static const GTypeInfo tinfo = + { + sizeof (GailCanvasGroupFactoryClass), + (GBaseInitFunc) NULL, /* base init */ + (GBaseFinalizeFunc) NULL, /* base finalize */ + (GClassInitFunc) gail_canvas_group_factory_class_init, /* class init */ + (GClassFinalizeFunc) NULL, /* class finalize */ + NULL, /* class data */ + sizeof (GailCanvasGroupFactory), /* instance size */ + 0, /* nb preallocs */ + (GInstanceInitFunc) NULL, /* instance init */ + NULL /* value table */ + }; + type = g_type_register_static ( + ATK_TYPE_OBJECT_FACTORY, + "GailCanvasGroupFactory" , &tinfo, 0); + } + + return type; +} + +static void +gail_canvas_group_factory_class_init (GailCanvasGroupFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = gail_canvas_group_factory_create_accessible; + class->get_accessible_type = gail_canvas_group_factory_get_accessible_type; +} + +static AtkObject* +gail_canvas_group_factory_create_accessible (GObject *obj) +{ + return gail_canvas_group_new (obj); +} + +static GType +gail_canvas_group_factory_get_accessible_type (void) +{ + return GAIL_TYPE_CANVAS_GROUP; +} diff --git a/libgnomecanvas/gailcanvasgroupfactory.h b/libgnomecanvas/gailcanvasgroupfactory.h new file mode 100644 index 0000000000..7ba4014eb6 --- /dev/null +++ b/libgnomecanvas/gailcanvasgroupfactory.h @@ -0,0 +1,53 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_GROUP_FACTORY_H__ +#define __GAIL_CANVAS_GROUP_FACTORY_H__ + +#include <atk/atkobjectfactory.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_GROUP_FACTORY (gail_canvas_group_factory_get_type ()) +#define GAIL_CANVAS_GROUP_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_GROUP_FACTORY, GailCanvasGroupFactory)) +#define GAIL_CANVAS_GROUP_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_GROUP_FACTORY, GailCanvasGroupFactoryClass)) +#define GAIL_IS_CANVAS_GROUP_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_GROUP_FACTORY)) +#define GAIL_IS_CANVAS_GROUP_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_GROUP_FACTORY)) +#define GAIL_CANVAS_GROUP_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_GROUP_FACTORY, GailCanvasGroupFactoryClass)) + + +typedef struct _GailCanvasGroupFactory GailCanvasGroupFactory; +typedef struct _GailCanvasGroupFactoryClass GailCanvasGroupFactoryClass; + +struct _GailCanvasGroupFactory +{ + AtkObjectFactory parent; +}; + +struct _GailCanvasGroupFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + +GType gail_canvas_group_factory_get_type(void); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_GROUP_FACTORY_H__ */ + diff --git a/libgnomecanvas/gailcanvasitem.c b/libgnomecanvas/gailcanvasitem.c new file mode 100644 index 0000000000..f059e1909c --- /dev/null +++ b/libgnomecanvas/gailcanvasitem.c @@ -0,0 +1,514 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <math.h> +#include <gtk/gtk.h> +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-util.h> +#include "gailcanvasitem.h" +#include <libgail-util/gailmisc.h> + +static void gail_canvas_item_initialize (AtkObject *obj, + gpointer data); +static AtkObject* gail_canvas_item_get_parent (AtkObject *obj); +static gint gail_canvas_item_get_index_in_parent (AtkObject *obj); +static AtkStateSet* gail_canvas_item_ref_state_set (AtkObject *obj); + +static void gail_canvas_item_component_interface_init (AtkComponentIface *iface); +static guint gail_canvas_item_add_focus_handler (AtkComponent *component, + AtkFocusHandler handler); +static void gail_canvas_item_get_extents (AtkComponent *component, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coord_type); +static gint gail_canvas_item_get_mdi_zorder (AtkComponent *component); +static gboolean gail_canvas_item_grab_focus (AtkComponent *component); +static void gail_canvas_item_remove_focus_handler (AtkComponent *component, + guint handler_id); +static gboolean is_item_on_screen (GnomeCanvasItem *item); +static void get_item_extents (GnomeCanvasItem *item, + gint *x, + gint *y, + gint *width, + gint *height); +static gboolean is_item_in_window (GnomeCanvasItem *item, + gint x, + gint y, + gint width, + gint height); + +static AtkGObjectAccessibleClass *parent_class = NULL; + +G_DEFINE_TYPE_WITH_CODE (GailCanvasItem, + gail_canvas_item, + ATK_TYPE_GOBJECT_ACCESSIBLE, + G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, + gail_canvas_item_component_interface_init)); + +static void +gail_canvas_item_init (GailCanvasItem *foo) +{ + ; +} + +AtkObject* +gail_canvas_item_new (GObject *obj) +{ + gpointer object; + AtkObject *atk_object; + + g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (obj), NULL); + object = g_object_new (GAIL_TYPE_CANVAS_ITEM, NULL); + atk_object = ATK_OBJECT (object); + atk_object_initialize (atk_object, obj); + atk_object->role = ATK_ROLE_UNKNOWN; + return atk_object; +} + +static void +gail_canvas_item_initialize (AtkObject *obj, + gpointer data) +{ + ATK_OBJECT_CLASS (parent_class)->initialize (obj, data); + + g_object_set_data (G_OBJECT (obj), "atk-component-layer", + GINT_TO_POINTER (ATK_LAYER_MDI)); +} + +static void +gail_canvas_item_class_init (GailCanvasItemClass *klass) +{ + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + class->get_parent = gail_canvas_item_get_parent; + class->get_index_in_parent = gail_canvas_item_get_index_in_parent; + class->ref_state_set = gail_canvas_item_ref_state_set; + class->initialize = gail_canvas_item_initialize; +} + +static AtkObject * +gail_canvas_item_get_parent (AtkObject *obj) +{ + AtkGObjectAccessible *atk_gobj; + GObject *g_obj; + GnomeCanvasItem *item; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (obj), NULL); + if (obj->accessible_parent) + return obj->accessible_parent; + atk_gobj = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + /* Object is defunct */ + return NULL; + + item = GNOME_CANVAS_ITEM (g_obj); + if (item->parent) + return atk_gobject_accessible_for_object (G_OBJECT (item->parent)); + else + return gtk_widget_get_accessible (GTK_WIDGET (item->canvas)); +} + +static gint +gail_canvas_item_get_index_in_parent (AtkObject *obj) +{ + AtkGObjectAccessible *atk_gobj; + GObject *g_obj; + GnomeCanvasItem *item; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (obj), -1); + if (obj->accessible_parent) + { + gint n_children, i; + gboolean found = FALSE; + + n_children = atk_object_get_n_accessible_children (obj->accessible_parent); + for (i = 0; i < n_children; i++) + { + AtkObject *child; + + child = atk_object_ref_accessible_child (obj->accessible_parent, i); + if (child == obj) + found = TRUE; + + g_object_unref (child); + if (found) + return i; + } + return -1; + } + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + /* Object is defunct */ + return -1; + + item = GNOME_CANVAS_ITEM (g_obj); + if (item->parent) + { + return g_list_index (GNOME_CANVAS_GROUP (item->parent)->item_list, item); + } + else + { + g_return_val_if_fail (item->canvas->root == item, -1); + return 0; + } +} + +static AtkStateSet* +gail_canvas_item_ref_state_set (AtkObject *obj) +{ + AtkGObjectAccessible *atk_gobj; + GObject *g_obj; + GnomeCanvasItem *item; + AtkStateSet *state_set; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (obj), NULL); + atk_gobj = ATK_GOBJECT_ACCESSIBLE (obj); + + state_set = ATK_OBJECT_CLASS (parent_class)->ref_state_set (obj); + + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + { + /* Object is defunct */ + atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT); + } + else + { + item = GNOME_CANVAS_ITEM (g_obj); + + if (item->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + { + atk_state_set_add_state (state_set, ATK_STATE_VISIBLE); + if (is_item_on_screen (item)) + { + atk_state_set_add_state (state_set, ATK_STATE_SHOWING); + } + } + if (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas))) + { + atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE); + + if (item->canvas->focused_item == item) + { + atk_state_set_add_state (state_set, ATK_STATE_FOCUSED); + } + } + } + + return state_set; +} + +static void +gail_canvas_item_component_interface_init (AtkComponentIface *iface) +{ + g_return_if_fail (iface != NULL); + + iface->add_focus_handler = gail_canvas_item_add_focus_handler; + iface->get_extents = gail_canvas_item_get_extents; + iface->get_mdi_zorder = gail_canvas_item_get_mdi_zorder; + iface->grab_focus = gail_canvas_item_grab_focus; + iface->remove_focus_handler = gail_canvas_item_remove_focus_handler; +} + +static guint +gail_canvas_item_add_focus_handler (AtkComponent *component, + AtkFocusHandler handler) +{ + GSignalMatchType match_type; + gulong ret; + guint signal_id; + + match_type = G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC; + signal_id = g_signal_lookup ("focus-event", ATK_TYPE_OBJECT); + + ret = g_signal_handler_find (component, match_type, signal_id, 0, NULL, + (gpointer) handler, NULL); + if (!ret) + { + return g_signal_connect_closure_by_id (component, + signal_id, 0, + g_cclosure_new ( + G_CALLBACK (handler), NULL, + (GClosureNotify) NULL), + FALSE); + } + else + { + return 0; + } +} + +static void +gail_canvas_item_get_extents (AtkComponent *component, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coord_type) +{ + AtkGObjectAccessible *atk_gobj; + GObject *obj; + GnomeCanvasItem *item; + gint window_x, window_y; + gint toplevel_x, toplevel_y; + gint local_x, local_y; + + g_return_if_fail (GAIL_IS_CANVAS_ITEM (component)); + atk_gobj = ATK_GOBJECT_ACCESSIBLE (component); + obj = atk_gobject_accessible_get_object (atk_gobj); + + if (obj == NULL) + /* item is defunct */ + return; + + /* Get the GnomeCanvasItem */ + item = GNOME_CANVAS_ITEM (obj); + + /* If this item has no parent canvas, something's broken */ + g_return_if_fail (GTK_IS_WIDGET (item->canvas)); + + get_item_extents (item, &local_x, &local_y, width, height); + if (!is_item_in_window (item, local_x, local_y, *width, *height)) + { + *x = G_MININT; + *y = G_MININT; + return; + } + + gail_misc_get_origins (GTK_WIDGET (item->canvas), &window_x, &window_y, + &toplevel_x, &toplevel_y); + *x = local_x + window_x - toplevel_x; + *y = local_y + window_y - toplevel_y; + + /* If screen coordinates are requested, modify x and y appropriately */ + if (coord_type == ATK_XY_SCREEN) + { + *x += toplevel_x; + *y += toplevel_y; + } + return; +} + +static gint +gail_canvas_item_get_mdi_zorder (AtkComponent *component) +{ + g_return_val_if_fail (ATK_OBJECT (component), -1); + + return gail_canvas_item_get_index_in_parent (ATK_OBJECT (component)); +} + +static gboolean +gail_canvas_item_grab_focus (AtkComponent *component) +{ + AtkGObjectAccessible *atk_gobj; + GObject *obj; + GnomeCanvasItem *item; + GtkWidget *toplevel; + + g_return_val_if_fail (GAIL_IS_CANVAS_ITEM (component), FALSE); + atk_gobj = ATK_GOBJECT_ACCESSIBLE (component); + obj = atk_gobject_accessible_get_object (atk_gobj); + + /* Get the GnomeCanvasItem */ + item = GNOME_CANVAS_ITEM (obj); + if (item == NULL) + /* item is defunct */ + return FALSE; + + gnome_canvas_item_grab_focus (item); + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item->canvas)); + if (gtk_widget_is_toplevel (toplevel)) + gtk_window_present (GTK_WINDOW (toplevel)); + + return TRUE; +} + +static void +gail_canvas_item_remove_focus_handler (AtkComponent *component, + guint handler_id) +{ + g_signal_handler_disconnect (ATK_OBJECT (component), handler_id); +} + +static gboolean +is_item_on_screen (GnomeCanvasItem *item) +{ + gint x, y, width, height; + + get_item_extents (item, &x, &y, &width, &height); + return is_item_in_window (item, x, y, width, height); +} + +static void +get_item_extents (GnomeCanvasItem *item, + gint *x, + gint *y, + gint *width, + gint *height) +{ + double bx1, by1, bx2, by2; + double i2c[6]; + ArtPoint p1, p2, p3, p4; + ArtPoint q1, q2, q3, q4; + double min_x1, min_y1, min_x2, min_y2; + double max_x1, max_y1, max_x2, max_y2; + int x1, y1, x2, y2; + int scroll_x, scroll_y; + + /* Get the bounding box in item-relative coordinates */ + + bx1 = by1 = bx2 = by2 = 0.0; + + if (GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (item))->bounds) + (* GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (item))->bounds) (item, &bx1, &by1, &bx2, &by2); + + /* Get the item coordinates -> canvas pixel coordinates affine */ + + gnome_canvas_item_i2c_affine (item, i2c); + + /* Convert the bounding box to canvas pixel coordinates and find its minimum + * surrounding rectangle. + */ + + p1.x = p2.x = bx1; + p1.y = p4.y = by1; + p3.x = p4.x = bx2; + p2.y = p3.y = by2; + + art_affine_point (&q1, &p1, i2c); + art_affine_point (&q2, &p2, i2c); + art_affine_point (&q3, &p3, i2c); + art_affine_point (&q4, &p4, i2c); + + if (q1.x < q2.x) + { + min_x1 = q1.x; + max_x1 = q2.x; + } + else + { + min_x1 = q2.x; + max_x1 = q1.x; + } + + if (q1.y < q2.y) + { + min_y1 = q1.y; + max_y1 = q2.y; + } + else + { + min_y1 = q2.y; + max_y1 = q1.y; + } + + if (q3.x < q4.x) + { + min_x2 = q3.x; + max_x2 = q4.x; + } + else + { + min_x2 = q4.x; + max_x2 = q3.x; + } + + if (q3.y < q4.y) + { + min_y2 = q3.y; + max_y2 = q4.y; + } + else + { + min_y2 = q4.y; + max_y2 = q3.y; + } + + bx1 = MIN (min_x1, min_x2); + by1 = MIN (min_y1, min_y2); + bx2 = MAX (max_x1, max_x2); + by2 = MAX (max_y1, max_y2); + + /* Convert to integer coordinates */ + + x1 = floor (bx1); + y1 = floor (by1); + x2 = ceil (bx2); + y2 = ceil (by2); + + gnome_canvas_get_scroll_offsets (item->canvas, &scroll_x, &scroll_y); + + if (x) + *x = x1 - scroll_x; + + if (y) + *y = y1 - scroll_y; + + if (width) + *width = x2 - x1; + + if (height) + *height = y2 - y1; +} + +static gboolean +is_item_in_window (GnomeCanvasItem *item, + gint x, + gint y, + gint width, + gint height) +{ + GtkWidget *widget; + gboolean retval; + + widget = GTK_WIDGET (item->canvas); + if (widget->window) + { + int window_width, window_height; + + gdk_window_get_geometry (widget->window, NULL, NULL, + &window_width, &window_height, NULL); + /* + * Check whether rectangles intersect + */ + if (x + width <= 0 || + y + height <= 0 || + x > window_width || + y > window_height) + { + retval = FALSE; + } + else + { + retval = TRUE; + } + } + else + { + retval = FALSE; + } + return retval; +} diff --git a/libgnomecanvas/gailcanvasitem.h b/libgnomecanvas/gailcanvasitem.h new file mode 100644 index 0000000000..9f89d7bbc3 --- /dev/null +++ b/libgnomecanvas/gailcanvasitem.h @@ -0,0 +1,54 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_ITEM_H__ +#define __GAIL_CANVAS_ITEM_H__ + +#include <libgnomecanvas/gnome-canvas.h> +#include <atk/atk.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_ITEM (gail_canvas_item_get_type ()) +#define GAIL_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_ITEM, GailCanvasItem)) +#define GAIL_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_ITEM, GailCanvasItemClass)) +#define GAIL_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_ITEM)) +#define GAIL_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_ITEM)) +#define GAIL_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_ITEM, GailCanvasItemClass)) + +typedef struct _GailCanvasItem GailCanvasItem; +typedef struct _GailCanvasItemClass GailCanvasItemClass; + +struct _GailCanvasItem +{ + AtkGObjectAccessible parent; +}; + +GType gail_canvas_item_get_type (void); + +struct _GailCanvasItemClass +{ + AtkGObjectAccessibleClass parent_class; +}; + +AtkObject* gail_canvas_item_new (GObject *obj); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_ITEM_H__ */ diff --git a/libgnomecanvas/gailcanvasitemfactory.c b/libgnomecanvas/gailcanvasitemfactory.c new file mode 100644 index 0000000000..5f59ef2823 --- /dev/null +++ b/libgnomecanvas/gailcanvasitemfactory.c @@ -0,0 +1,57 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include "gailcanvasitemfactory.h" +#include "gailcanvasitem.h" + +static AtkObject* gail_canvas_item_factory_create_accessible (GObject *obj); + +static GType gail_canvas_item_factory_get_accessible_type (void); + +G_DEFINE_TYPE (GailCanvasItemFactory, + gail_canvas_item_factory, + ATK_TYPE_OBJECT_FACTORY); + +static void +gail_canvas_item_factory_init (GailCanvasItemFactory *foo) +{ + ; +} + +static void +gail_canvas_item_factory_class_init (GailCanvasItemFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = gail_canvas_item_factory_create_accessible; + class->get_accessible_type = gail_canvas_item_factory_get_accessible_type; +} + +static AtkObject* +gail_canvas_item_factory_create_accessible (GObject *obj) +{ + return gail_canvas_item_new (obj); +} + +static GType +gail_canvas_item_factory_get_accessible_type (void) +{ + return GAIL_TYPE_CANVAS_ITEM; +} diff --git a/libgnomecanvas/gailcanvasitemfactory.h b/libgnomecanvas/gailcanvasitemfactory.h new file mode 100644 index 0000000000..dd55815cbe --- /dev/null +++ b/libgnomecanvas/gailcanvasitemfactory.h @@ -0,0 +1,53 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_ITEM_FACTORY_H__ +#define __GAIL_CANVAS_ITEM_FACTORY_H__ + +#include <atk/atkobjectfactory.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_ITEM_FACTORY (gail_canvas_item_factory_get_type ()) +#define GAIL_CANVAS_ITEM_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_ITEM_FACTORY, GailCanvasItemFactory)) +#define GAIL_CANVAS_ITEM_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_ITEM_FACTORY, GailCanvasItemFactoryClass)) +#define GAIL_IS_CANVAS_ITEM_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_ITEM_FACTORY)) +#define GAIL_IS_CANVAS_ITEM_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_ITEM_FACTORY)) +#define GAIL_CANVAS_ITEM_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_ITEM_FACTORY, GailCanvasItemFactoryClass)) + + +typedef struct _GailCanvasItemFactory GailCanvasItemFactory; +typedef struct _GailCanvasItemFactoryClass GailCanvasItemFactoryClass; + +struct _GailCanvasItemFactory +{ + AtkObjectFactory parent; +}; + +struct _GailCanvasItemFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + +GType gail_canvas_item_factory_get_type(void); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_ITEM_FACTORY_H__ */ + diff --git a/libgnomecanvas/gailcanvastext.c b/libgnomecanvas/gailcanvastext.c new file mode 100644 index 0000000000..542683c6c3 --- /dev/null +++ b/libgnomecanvas/gailcanvastext.c @@ -0,0 +1,522 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include <libgnomecanvas/libgnomecanvas.h> +#include "gailcanvasitem.h" +#include "gailcanvastext.h" +#include <libgail-util/gail-util.h> + +struct _GailCanvasText +{ + GailCanvasItem parent; + GailTextUtil *textutil; +}; + +static void gail_canvas_text_text_interface_init (AtkTextIface *iface); +static gchar* gail_canvas_text_get_text (AtkText *text, + gint start_offset, + gint end_offset); +static gchar* gail_canvas_text_get_text_after_offset + (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset); +static gchar* gail_canvas_text_get_text_at_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset); +static gchar* gail_canvas_text_get_text_before_offset + (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset); +static gunichar gail_canvas_text_get_character_at_offset + (AtkText *text, + gint offset); +static gint gail_canvas_text_get_character_count (AtkText *text); +static gint gail_canvas_text_get_caret_offset (AtkText *text); +static gboolean gail_canvas_text_set_caret_offset (AtkText *text, + gint offset); +static gint gail_canvas_text_get_offset_at_point (AtkText *text, + gint x, + gint y, + AtkCoordType coords); +static void gail_canvas_text_get_character_extents (AtkText *text, + gint offset, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coords); +static AtkAttributeSet* + gail_canvas_text_get_run_attributes (AtkText *text, + gint offset, + gint *start_offset, + gint *end_offset); +static AtkAttributeSet* + gail_canvas_text_get_default_attributes (AtkText *text); +static gint gail_canvas_text_get_n_selections (AtkText *text); +static gchar* gail_canvas_text_get_selection (AtkText *text, + gint selection_num, + gint *start_pos, + gint *end_pos); +static gboolean gail_canvas_text_add_selection (AtkText *text, + gint start_pos, + gint end_pos); +static gboolean gail_canvas_text_remove_selection (AtkText *text, + gint selection_num); +static gboolean gail_canvas_text_set_selection (AtkText *text, + gint selection_num, + gint start_pos, + gint end_pos); +static gchar* get_text_near_offset (AtkText *text, + GailOffsetType function, + AtkTextBoundary boundary_type, + gint offset, + gint *start_offset, + gint *end_offset); + +G_DEFINE_TYPE_WITH_CODE(GailCanvasText, + gail_canvas_text, + GAIL_TYPE_CANVAS_ITEM, + G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, + gail_canvas_text_text_interface_init);) + +static void +gail_canvas_text_init (GailCanvasText *foo) +{ + ; +} + +AtkObject* +gail_canvas_text_new (GObject *obj) +{ + gpointer object; + AtkObject *atk_object; + GailCanvasText *gail_text; + + g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (obj), NULL); + object = g_object_new (GAIL_TYPE_CANVAS_TEXT, NULL); + atk_object = ATK_OBJECT (object); + gail_text = GAIL_CANVAS_TEXT (object); + + atk_object_initialize (atk_object, obj); + gail_text->textutil = gail_text_util_new (); + + if (GNOME_IS_CANVAS_RICH_TEXT (obj)) + { + gail_text_util_buffer_setup (gail_text->textutil, + gnome_canvas_rich_text_get_buffer (GNOME_CANVAS_RICH_TEXT (obj))); + } + else if (GNOME_IS_CANVAS_TEXT (obj)) + { + gail_text_util_text_setup (gail_text->textutil, + GNOME_CANVAS_TEXT (obj)->text); + } + + atk_object->role = ATK_ROLE_TEXT; + return atk_object; +} + +static void +gail_canvas_text_class_init (GailCanvasTextClass *klass) +{ +} + +static void +gail_canvas_text_text_interface_init (AtkTextIface *iface) +{ + g_return_if_fail (iface != NULL); + + iface->get_text = gail_canvas_text_get_text; + iface->get_text_after_offset = gail_canvas_text_get_text_after_offset; + iface->get_text_at_offset = gail_canvas_text_get_text_at_offset; + iface->get_text_before_offset = gail_canvas_text_get_text_before_offset; + iface->get_character_at_offset = gail_canvas_text_get_character_at_offset; + iface->get_character_count = gail_canvas_text_get_character_count; + iface->get_caret_offset = gail_canvas_text_get_caret_offset; + iface->set_caret_offset = gail_canvas_text_set_caret_offset; + iface->get_offset_at_point = gail_canvas_text_get_offset_at_point; + iface->get_character_extents = gail_canvas_text_get_character_extents; + iface->get_n_selections = gail_canvas_text_get_n_selections; + iface->get_selection = gail_canvas_text_get_selection; + iface->add_selection = gail_canvas_text_add_selection; + iface->remove_selection = gail_canvas_text_remove_selection; + iface->set_selection = gail_canvas_text_set_selection; + iface->get_run_attributes = gail_canvas_text_get_run_attributes; + iface->get_default_attributes = gail_canvas_text_get_default_attributes; +} + +static gchar* +gail_canvas_text_get_text (AtkText *text, + gint start_offset, + gint end_offset) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter start, end; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), NULL); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, NULL); + + buffer = gail_text->textutil->buffer; + gtk_text_buffer_get_iter_at_offset (buffer, &start, start_offset); + gtk_text_buffer_get_iter_at_offset (buffer, &end, end_offset); + + return gtk_text_buffer_get_text (buffer, &start, &end, FALSE); +} + +static gchar* +gail_canvas_text_get_text_after_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + return get_text_near_offset (text, GAIL_AFTER_OFFSET, + boundary_type, offset, + start_offset, end_offset); +} + +static gchar* +gail_canvas_text_get_text_at_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + return get_text_near_offset (text, GAIL_AT_OFFSET, + boundary_type, offset, + start_offset, end_offset); +} + +static gchar* +gail_canvas_text_get_text_before_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + return get_text_near_offset (text, GAIL_BEFORE_OFFSET, + boundary_type, offset, + start_offset, end_offset); +} + +static gunichar +gail_canvas_text_get_character_at_offset (AtkText *text, + gint offset) +{ + GailCanvasText *gail_item; + GtkTextIter start, end; + GtkTextBuffer *buffer; + gchar *string; + gchar *index; + gunichar unichar; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), '\0'); + gail_item = GAIL_CANVAS_TEXT (text); + buffer = gail_item->textutil->buffer; + if (offset >= gtk_text_buffer_get_char_count (buffer)) + return '\0'; + + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + string = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + index = g_utf8_offset_to_pointer (string, offset); + + unichar = g_utf8_get_char (index); + g_free (string); + return unichar; +} + +static gint +gail_canvas_text_get_character_count (AtkText *text) +{ + GtkTextBuffer *buffer; + GailCanvasText *gail_text; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), 0); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, 0); + buffer = gail_text->textutil->buffer; + return gtk_text_buffer_get_char_count (buffer); +} + +static gint +gail_canvas_text_get_caret_offset (AtkText *text) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextMark *cursor_mark; + GtkTextIter cursor_itr; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), 0); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, 0); + buffer = gail_text->textutil->buffer; + cursor_mark = gtk_text_buffer_get_insert (buffer); + gtk_text_buffer_get_iter_at_mark (buffer, &cursor_itr, cursor_mark); + return gtk_text_iter_get_offset (&cursor_itr); +} + +static gboolean +gail_canvas_text_set_caret_offset (AtkText *text, + gint offset) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter pos_itr; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), FALSE); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, FALSE); + buffer = gail_text->textutil->buffer; + gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, offset); + gtk_text_buffer_move_mark_by_name (buffer, "insert", &pos_itr); + return TRUE; +} + +static gint +gail_canvas_text_get_offset_at_point (AtkText *text, + gint x, + gint y, + AtkCoordType coords) +{ + return -1; +} + +static void +gail_canvas_text_get_character_extents (AtkText *text, + gint offset, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coords) +{ + return; +} + +static AtkAttributeSet* +gail_canvas_text_get_run_attributes (AtkText *text, + gint offset, + gint *start_offset, + gint *end_offset) +{ + GailCanvasText *gail_text; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), NULL); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, NULL); + + return gail_misc_buffer_get_run_attributes (gail_text->textutil->buffer, + offset, start_offset, end_offset); +} + +static AtkAttributeSet* +gail_canvas_text_get_default_attributes (AtkText *text) +{ + return NULL; +} + +static gint +gail_canvas_text_get_n_selections (AtkText *text) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter start, end; + gint select_start, select_end; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), -1); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, -1); + buffer = gail_text->textutil->buffer; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + select_start = gtk_text_iter_get_offset (&start); + select_end = gtk_text_iter_get_offset (&end); + + if (select_start != select_end) + return 1; + else + return 0; +} + +static gchar* +gail_canvas_text_get_selection (AtkText *text, + gint selection_num, + gint *start_pos, + gint *end_pos) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter start, end; + + /* Only let the user get the selection if one is set, and if the + * selection_num is 0. + */ + if (selection_num != 0) + return NULL; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), NULL); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, NULL); + buffer = gail_text->textutil->buffer; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + *start_pos = gtk_text_iter_get_offset (&start); + *end_pos = gtk_text_iter_get_offset (&end); + + if (*start_pos != *end_pos) + return gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + else + return NULL; +} + +static gboolean +gail_canvas_text_add_selection (AtkText *text, + gint start_pos, + gint end_pos) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter pos_itr; + GtkTextIter start, end; + gint select_start, select_end; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), FALSE); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, FALSE); + buffer = gail_text->textutil->buffer; + + gtk_text_buffer_get_selection_bounds (buffer, &start, &end); + select_start = gtk_text_iter_get_offset (&start); + select_end = gtk_text_iter_get_offset (&end); + + /* If there is already a selection, then don't allow another to be added, + * since GtkTextView only supports one selected region. + */ + if (select_start == select_end) + { + gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, start_pos); + gtk_text_buffer_move_mark_by_name (buffer, "insert", &pos_itr); + gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, end_pos); + gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &pos_itr); + return TRUE; + } + else + return FALSE; +} + +static gboolean +gail_canvas_text_remove_selection (AtkText *text, + gint selection_num) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextMark *cursor_mark; + GtkTextIter cursor_itr; + GtkTextIter start, end; + gint select_start, select_end; + + if (selection_num != 0) + return FALSE; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), FALSE); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, FALSE); + buffer = gail_text->textutil->buffer; + + gtk_text_buffer_get_selection_bounds(buffer, &start, &end); + select_start = gtk_text_iter_get_offset(&start); + select_end = gtk_text_iter_get_offset(&end); + + if (select_start != select_end) + { + /* Setting the start & end of the selected region to the caret position + * turns off the selection. + */ + cursor_mark = gtk_text_buffer_get_insert (buffer); + gtk_text_buffer_get_iter_at_mark (buffer, &cursor_itr, cursor_mark); + gtk_text_buffer_move_mark_by_name (buffer, "insert", &cursor_itr); + gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &cursor_itr); + return TRUE; + } + else + return FALSE; +} + + + +static gboolean +gail_canvas_text_set_selection (AtkText *text, + gint selection_num, + gint start_pos, + gint end_pos) +{ + GailCanvasText *gail_text; + GtkTextBuffer *buffer; + GtkTextIter pos_itr; + GtkTextIter start, end; + gint select_start, select_end; + + /* Only let the user move the selection if one is set, and if the + * selection_num is 0 + */ + if (selection_num != 0) + return FALSE; + + g_return_val_if_fail (GAIL_IS_CANVAS_TEXT (text), FALSE); + gail_text = GAIL_CANVAS_TEXT (text); + g_return_val_if_fail (gail_text->textutil, FALSE); + buffer = gail_text->textutil->buffer; + + gtk_text_buffer_get_selection_bounds(buffer, &start, &end); + select_start = gtk_text_iter_get_offset(&start); + select_end = gtk_text_iter_get_offset(&end); + + if (select_start != select_end) + { + gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, start_pos); + gtk_text_buffer_move_mark_by_name (buffer, "insert", &pos_itr); + gtk_text_buffer_get_iter_at_offset (buffer, &pos_itr, end_pos); + gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &pos_itr); + return TRUE; + } + else + return FALSE; +} + +static gchar* +get_text_near_offset (AtkText *text, + GailOffsetType function, + AtkTextBoundary boundary_type, + gint offset, + gint *start_offset, + gint *end_offset) +{ + return gail_text_util_get_text (GAIL_CANVAS_TEXT (text)->textutil, NULL, + function, boundary_type, offset, + start_offset, end_offset); +} diff --git a/libgnomecanvas/gailcanvastext.h b/libgnomecanvas/gailcanvastext.h new file mode 100644 index 0000000000..54a0239cc2 --- /dev/null +++ b/libgnomecanvas/gailcanvastext.h @@ -0,0 +1,52 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_TEXT_H__ +#define __GAIL_CANVAS_TEXT_H__ + +#include <libgnomecanvas/libgnomecanvas.h> +#include <atk/atk.h> +#include "gailcanvasitem.h" +#include <libgail-util/gailtextutil.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_TEXT (gail_canvas_text_get_type ()) +#define GAIL_CANVAS_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_TEXT, GailCanvasText)) +#define GAIL_CANVAS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_TEXT, GailCanvasTextClass)) +#define GAIL_IS_CANVAS_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_TEXT)) +#define GAIL_IS_CANVAS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_TEXT)) +#define GAIL_CANVAS_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_TEXT, GailCanvasTextClass)) + +typedef struct _GailCanvasText GailCanvasText; +typedef struct _GailCanvasTextClass GailCanvasTextClass; + +GType +gail_canvas_text_get_type (void); + +struct _GailCanvasTextClass +{ + GailCanvasItemClass parent_class; +}; + +AtkObject* gail_canvas_text_new (GObject *obj); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_TEXT_H__ */ diff --git a/libgnomecanvas/gailcanvastextfactory.c b/libgnomecanvas/gailcanvastextfactory.c new file mode 100644 index 0000000000..987e385530 --- /dev/null +++ b/libgnomecanvas/gailcanvastextfactory.c @@ -0,0 +1,57 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include "gailcanvastextfactory.h" +#include "gailcanvastext.h" + +static AtkObject * gail_canvas_text_factory_create_accessible (GObject *obj); + +static GType gail_canvas_text_factory_get_accessible_type (void); + +G_DEFINE_TYPE (GailCanvasTextFactory, + gail_canvas_text_factory, + ATK_TYPE_OBJECT_FACTORY); + +static void +gail_canvas_text_factory_init (GailCanvasTextFactory *foo) +{ + ; +} + +static void +gail_canvas_text_factory_class_init (GailCanvasTextFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = gail_canvas_text_factory_create_accessible; + class->get_accessible_type = gail_canvas_text_factory_get_accessible_type; +} + +static AtkObject* +gail_canvas_text_factory_create_accessible (GObject *obj) +{ + return gail_canvas_text_new (obj); +} + +static GType +gail_canvas_text_factory_get_accessible_type (void) +{ + return GAIL_TYPE_CANVAS_TEXT; +} diff --git a/libgnomecanvas/gailcanvastextfactory.h b/libgnomecanvas/gailcanvastextfactory.h new file mode 100644 index 0000000000..3d99f778e5 --- /dev/null +++ b/libgnomecanvas/gailcanvastextfactory.h @@ -0,0 +1,53 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_TEXT_FACTORY_H__ +#define __GAIL_CANVAS_TEXT_FACTORY_H__ + +#include <atk/atkobjectfactory.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_TEXT_FACTORY (gail_canvas_text_factory_get_type ()) +#define GAIL_CANVAS_TEXT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_TEXT_FACTORY, GailCanvasTextFactory)) +#define GAIL_CANVAS_TEXT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_TEXT_FACTORY, GailCanvasTextFactoryClass)) +#define GAIL_IS_CANVAS_TEXT_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_TEXT_FACTORY)) +#define GAIL_IS_CANVAS_TEXT_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_TEXT_FACTORY)) +#define GAIL_CANVAS_TEXT_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_TEXT_FACTORY, GailCanvasTextFactoryClass)) + + +typedef struct _GailCanvasTextFactory GailCanvasTextFactory; +typedef struct _GailCanvasTextFactoryClass GailCanvasTextFactoryClass; + +struct _GailCanvasTextFactory +{ + AtkObjectFactory parent; +}; + +struct _GailCanvasTextFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + +GType gail_canvas_text_factory_get_type(void); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_TEXT_FACTORY_H__ */ + diff --git a/libgnomecanvas/gailcanvaswidget.c b/libgnomecanvas/gailcanvaswidget.c new file mode 100644 index 0000000000..c3d38f0699 --- /dev/null +++ b/libgnomecanvas/gailcanvaswidget.c @@ -0,0 +1,113 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <gtk/gtk.h> +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-widget.h> +#include "gailcanvaswidget.h" + +static gint gail_canvas_widget_get_n_children (AtkObject *obj); +static AtkObject* gail_canvas_widget_ref_child (AtkObject *obj, + gint i); + +G_DEFINE_TYPE (GailCanvasWidget, + gail_canvas_widget, + GAIL_TYPE_CANVAS_ITEM); + +static void +gail_canvas_widget_init (GailCanvasWidget *foo) +{ + ; +} + +AtkObject* +gail_canvas_widget_new (GObject *obj) +{ + gpointer object; + AtkObject *atk_object; + + g_return_val_if_fail (GNOME_IS_CANVAS_WIDGET (obj), NULL); + object = g_object_new (GAIL_TYPE_CANVAS_WIDGET, NULL); + atk_object = ATK_OBJECT (object); + atk_object_initialize (atk_object, obj); + atk_object->role = ATK_ROLE_PANEL; + return atk_object; +} + +static void +gail_canvas_widget_class_init (GailCanvasWidgetClass *klass) +{ + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + + class->get_n_children = gail_canvas_widget_get_n_children; + class->ref_child = gail_canvas_widget_ref_child; +} + +static gint +gail_canvas_widget_get_n_children (AtkObject *obj) +{ + AtkGObjectAccessible *atk_gobj; + GnomeCanvasWidget *canvas_widget; + GObject *g_obj; + + g_return_val_if_fail (GAIL_IS_CANVAS_WIDGET (obj), 0); + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + /* State is defunct */ + return 0; + + g_return_val_if_fail (GNOME_IS_CANVAS_WIDGET (g_obj), 0); + + canvas_widget = GNOME_CANVAS_WIDGET (g_obj); + g_return_val_if_fail (canvas_widget->widget, 0); + return 1; +} + +static AtkObject * +gail_canvas_widget_ref_child (AtkObject *obj, + gint i) +{ + AtkGObjectAccessible *atk_gobj; + GnomeCanvasWidget *canvas_widget; + GObject *g_obj; + AtkObject *atk_child; + + g_return_val_if_fail (GAIL_IS_CANVAS_WIDGET (obj), NULL); + + if (i != 0) + return NULL; + + atk_gobj = ATK_GOBJECT_ACCESSIBLE (obj); + g_obj = atk_gobject_accessible_get_object (atk_gobj); + if (g_obj == NULL) + /* State is defunct */ + return NULL; + + g_return_val_if_fail (GNOME_IS_CANVAS_WIDGET (g_obj), NULL); + + canvas_widget = GNOME_CANVAS_WIDGET (g_obj); + g_return_val_if_fail (canvas_widget->widget, NULL); + + atk_child = gtk_widget_get_accessible (canvas_widget->widget); + g_object_ref (atk_child); + atk_object_set_parent (atk_child, obj); + return atk_child; +} diff --git a/libgnomecanvas/gailcanvaswidget.h b/libgnomecanvas/gailcanvaswidget.h new file mode 100644 index 0000000000..016c7a7313 --- /dev/null +++ b/libgnomecanvas/gailcanvaswidget.h @@ -0,0 +1,55 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_WIDGET_H__ +#define __GAIL_CANVAS_WIDGET_H__ + +#include <libgnomecanvas/gnome-canvas.h> +#include <atk/atk.h> +#include "gailcanvasitem.h" + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_WIDGET (gail_canvas_widget_get_type ()) +#define GAIL_CANVAS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_WIDGET, GailCanvasWidget)) +#define GAIL_CANVAS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_WIDGET, GailCanvasWidgetClass)) +#define GAIL_IS_CANVAS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_WIDGET)) +#define GAIL_IS_CANVAS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_WIDGET)) +#define GAIL_CANVAS_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_WIDGET, GailCanvasWidgetClass)) + +typedef struct _GailCanvasWidget GailCanvasWidget; +typedef struct _GailCanvasWidgetClass GailCanvasWidgetClass; + +struct _GailCanvasWidget +{ + GailCanvasItem parent; +}; + +GType gail_canvas_widget_get_type (void); + +struct _GailCanvasWidgetClass +{ + GailCanvasItemClass parent_class; +}; + +AtkObject* gail_canvas_widget_new (GObject *obj); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_WIDGET_H__ */ diff --git a/libgnomecanvas/gailcanvaswidgetfactory.c b/libgnomecanvas/gailcanvaswidgetfactory.c new file mode 100644 index 0000000000..5923c165d2 --- /dev/null +++ b/libgnomecanvas/gailcanvaswidgetfactory.c @@ -0,0 +1,56 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gailcanvaswidgetfactory.h" +#include "gailcanvaswidget.h" + +static AtkObject * gail_canvas_widget_factory_create_accessible (GObject *obj); + +static GType gail_canvas_widget_factory_get_accessible_type (void); + +G_DEFINE_TYPE (GailCanvasWidgetFactory, + gail_canvas_widget_factory, + ATK_TYPE_OBJECT_FACTORY); + +static void +gail_canvas_widget_factory_init (GailCanvasWidgetFactory *foo) +{ + ; +} + +static void +gail_canvas_widget_factory_class_init (GailCanvasWidgetFactoryClass *klass) +{ + AtkObjectFactoryClass *class = ATK_OBJECT_FACTORY_CLASS (klass); + + class->create_accessible = gail_canvas_widget_factory_create_accessible; + class->get_accessible_type = gail_canvas_widget_factory_get_accessible_type; +} + +static AtkObject* +gail_canvas_widget_factory_create_accessible (GObject *obj) +{ + return gail_canvas_widget_new (obj); +} + +static GType +gail_canvas_widget_factory_get_accessible_type (void) +{ + return GAIL_TYPE_CANVAS_WIDGET; +} diff --git a/libgnomecanvas/gailcanvaswidgetfactory.h b/libgnomecanvas/gailcanvaswidgetfactory.h new file mode 100644 index 0000000000..caca8131a2 --- /dev/null +++ b/libgnomecanvas/gailcanvaswidgetfactory.h @@ -0,0 +1,53 @@ +/* GAIL - The GNOME Accessibility Implementation Library + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GAIL_CANVAS_WIDGET_FACTORY_H__ +#define __GAIL_CANVAS_WIDGET_FACTORY_H__ + +#include <atk/atkobjectfactory.h> + +G_BEGIN_DECLS + +#define GAIL_TYPE_CANVAS_WIDGET_FACTORY (gail_canvas_widget_factory_get_type ()) +#define GAIL_CANVAS_WIDGET_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GAIL_TYPE_CANVAS_WIDGET_FACTORY, GailCanvasWidgetFactory)) +#define GAIL_CANVAS_WIDGET_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GAIL_TYPE_CANVAS_WIDGET_FACTORY, GailCanvasWidgetFactoryClass)) +#define GAIL_IS_CANVAS_WIDGET_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GAIL_TYPE_CANVAS_WIDGET_FACTORY)) +#define GAIL_IS_CANVAS_WIDGET_FACTORY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GAIL_TYPE_CANVAS_WIDGET_FACTORY)) +#define GAIL_CANVAS_WIDGET_FACTORY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GAIL_TYPE_CANVAS_WIDGET_FACTORY, GailCanvasWidgetFactoryClass)) + + +typedef struct _GailCanvasWidgetFactory GailCanvasWidgetFactory; +typedef struct _GailCanvasWidgetFactoryClass GailCanvasWidgetFactoryClass; + +struct _GailCanvasWidgetFactory +{ + AtkObjectFactory parent; +}; + +struct _GailCanvasWidgetFactoryClass +{ + AtkObjectFactoryClass parent_class; +}; + +GType gail_canvas_widget_factory_get_type(void); + +G_END_DECLS + +#endif /* __GAIL_CANVAS_WIDGET_FACTORY_H__ */ + diff --git a/libgnomecanvas/gnome-canvas-bpath.c b/libgnomecanvas/gnome-canvas-bpath.c new file mode 100644 index 0000000000..f7f564a94d --- /dev/null +++ b/libgnomecanvas/gnome-canvas-bpath.c @@ -0,0 +1,175 @@ +/* Bpath item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@acm.org> + * Lauris Kaplinski <lauris@ximian.com> + * Miguel de Icaza <miguel@kernel.org> + * Cody Russell <bratsche@gnome.org> + * Rusty Conover <rconover@bangtail.net> + */ + +/* These includes are set up for standalone compile. If/when this codebase + is integrated into libgnomeui, the includes will need to change. */ + +#include <math.h> +#include <string.h> + +#include <gtk/gtk.h> +#include "gnome-canvas.h" +#include "gnome-canvas-util.h" + +#include "gnome-canvas-bpath.h" +#include "gnome-canvas-shape.h" +#include "gnome-canvas-shape-private.h" +#include "gnome-canvas-path-def.h" + +enum { + PROP_0, + PROP_BPATH +}; + +static void gnome_canvas_bpath_class_init (GnomeCanvasBpathClass *class); +static void gnome_canvas_bpath_init (GnomeCanvasBpath *bpath); +static void gnome_canvas_bpath_destroy (GtkObject *object); +static void gnome_canvas_bpath_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_bpath_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_bpath_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); + + +static GnomeCanvasShapeClass *parent_class; + +GType +gnome_canvas_bpath_get_type (void) +{ + static GType bpath_type; + + if (!bpath_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasBpathClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_bpath_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasBpath), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_bpath_init, + NULL /* value_table */ + }; + + bpath_type = g_type_register_static (GNOME_TYPE_CANVAS_SHAPE, "GnomeCanvasBpath", + &object_info, 0); + } + + return bpath_type; +} + +static void +gnome_canvas_bpath_class_init (GnomeCanvasBpathClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + /* when this gets checked into libgnomeui, change the + GTK_TYPE_POINTER to GTK_TYPE_GNOME_CANVAS_BPATH, and add an + entry to gnome-boxed.defs */ + + gobject_class->set_property = gnome_canvas_bpath_set_property; + gobject_class->get_property = gnome_canvas_bpath_get_property; + + object_class->destroy = gnome_canvas_bpath_destroy; + + g_object_class_install_property (gobject_class, + PROP_BPATH, + g_param_spec_boxed ("bpath", NULL, NULL, + GNOME_TYPE_CANVAS_PATH_DEF, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + item_class->update = gnome_canvas_bpath_update; +} + +static void +gnome_canvas_bpath_init (GnomeCanvasBpath *bpath) +{ + +} + +static void +gnome_canvas_bpath_destroy (GtkObject *object) +{ + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +gnome_canvas_bpath_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasPathDef *gpp; + + item = GNOME_CANVAS_ITEM (object); + + switch (param_id) { + case PROP_BPATH: + gpp = (GnomeCanvasPathDef*) g_value_get_boxed (value); + + gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (object), gpp); + + gnome_canvas_item_request_update (item); + break; + + default: + break; + } +} + + +static void +gnome_canvas_bpath_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasShape *shape; + + shape = GNOME_CANVAS_SHAPE(object); + + switch (param_id) { + case PROP_BPATH: + g_value_set_boxed (value, shape->priv->path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_bpath_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + if(GNOME_CANVAS_ITEM_CLASS(parent_class)->update) { + (* GNOME_CANVAS_ITEM_CLASS(parent_class)->update)(item, affine, clip_path, flags); + } +} diff --git a/libgnomecanvas/gnome-canvas-bpath.h b/libgnomecanvas/gnome-canvas-bpath.h new file mode 100644 index 0000000000..42a06a8d19 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-bpath.h @@ -0,0 +1,61 @@ +/* Bpath item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@acm.org> + * Lauris Kaplinski <lauris@ximian.com> + * Rusty Conover <rconover@bangtail.net> + */ + +#ifndef GNOME_CANVAS_BPATH_H +#define GNOME_CANVAS_BPATH_H + +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-shape.h> +#include <libgnomecanvas/gnome-canvas-path-def.h> + +G_BEGIN_DECLS + + +/* Bpath item for the canvas. + * + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * bpath GnomeCanvasPathDef * RW Pointer to an GnomeCanvasPathDef structure. + * This can be created by a call to + * gp_path_new() in (gp-path.h). + */ + +#define GNOME_TYPE_CANVAS_BPATH (gnome_canvas_bpath_get_type ()) +#define GNOME_CANVAS_BPATH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_BPATH, GnomeCanvasBpath)) +#define GNOME_CANVAS_BPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_BPATH, GnomeCanvasBpathClass)) +#define GNOME_IS_CANVAS_BPATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_BPATH)) +#define GNOME_IS_CANVAS_BPATH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_BPATH)) + + +typedef struct _GnomeCanvasBpath GnomeCanvasBpath; +typedef struct _GnomeCanvasBpathPriv GnomeCanvasBpathPriv; +typedef struct _GnomeCanvasBpathClass GnomeCanvasBpathClass; + +struct _GnomeCanvasBpath { + GnomeCanvasShape item; + +}; + +struct _GnomeCanvasBpathClass { + GnomeCanvasShapeClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_bpath_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-clipgroup.c b/libgnomecanvas/gnome-canvas-clipgroup.c new file mode 100644 index 0000000000..adfc749e47 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-clipgroup.c @@ -0,0 +1,450 @@ +#define GNOME_CANVAS_CLIPGROUP_C + +/* Clipping group for GnomeCanvas + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Author: + * Lauris Kaplinski <lauris@ximian.com> + */ + +/* These includes are set up for standalone compile. If/when this codebase + is integrated into libgnomeui, the includes will need to change. */ + +#include <math.h> +#include <string.h> + +#include <gtk/gtk.h> + +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_vpath_bpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_rect_svp.h> +#include <libart_lgpl/art_gray_svp.h> +#include <libart_lgpl/art_svp_intersect.h> +#include <libart_lgpl/art_svp_ops.h> + +#include "gnome-canvas.h" +#include "gnome-canvas-util.h" +#include "gnome-canvas-clipgroup.h" + +enum { + PROP_0, + PROP_PATH, + PROP_WIND +}; + +static void gnome_canvas_clipgroup_class_init (GnomeCanvasClipgroupClass *klass); +static void gnome_canvas_clipgroup_init (GnomeCanvasClipgroup *clipgroup); +static void gnome_canvas_clipgroup_destroy (GtkObject *object); +static void gnome_canvas_clipgroup_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_clipgroup_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gnome_canvas_clipgroup_update (GnomeCanvasItem *item, + double *affine, + ArtSVP *clip_path, + int flags); + +/* + * Generic clipping stuff + * + * This is somewhat slow and memory-hungry - we add extra + * composition, extra SVP render and allocate 65536 + * bytes for each clip level. It could be done more + * efficently per-object basis - but to make clipping + * universal, there is no alternative to double + * buffering (although it should be done into RGBA + * buffer by other method than ::render to make global + * opacity possible). + * Using art-render could possibly optimize that a bit, + * although I am not sure. + */ + +#define GCG_BUF_WIDTH 128 +#define GCG_BUF_HEIGHT 128 +#define GCG_BUF_PIXELS (GCG_BUF_WIDTH * GCG_BUF_HEIGHT) +#define GCG_BUF_SIZE (GCG_BUF_WIDTH * GCG_BUF_HEIGHT * 3) + +#define noSHOW_SHADOW + +static guchar *gcg_buf_new (void); +static void gcg_buf_free (guchar *buf); +static guchar *gcg_mask_new (void); +static void gcg_mask_free (guchar *mask); + +static void gnome_canvas_clipgroup_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); + +static GnomeCanvasGroupClass *parent_class; + +GType +gnome_canvas_clipgroup_get_type (void) +{ + static GType clipgroup_type; + + if (!clipgroup_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasClipgroupClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_clipgroup_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasClipgroup), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_clipgroup_init, + NULL /* value_table */ + }; + + clipgroup_type = g_type_register_static (GNOME_TYPE_CANVAS_GROUP, "GnomeCanvasClipgroup", + &object_info, 0); + } + + return clipgroup_type; +} + +static void +gnome_canvas_clipgroup_class_init (GnomeCanvasClipgroupClass *klass) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) klass; + object_class = (GtkObjectClass *) klass; + item_class = (GnomeCanvasItemClass *) klass; + parent_class = g_type_class_ref (GNOME_TYPE_CANVAS_GROUP); + + object_class->destroy = gnome_canvas_clipgroup_destroy; + gobject_class->set_property = gnome_canvas_clipgroup_set_property; + gobject_class->get_property = gnome_canvas_clipgroup_get_property; + item_class->update = gnome_canvas_clipgroup_update; + item_class->render = gnome_canvas_clipgroup_render; + + g_object_class_install_property (gobject_class, + PROP_PATH, + g_param_spec_pointer ("path", NULL, NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_WIND, + g_param_spec_uint ("wind", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); +} + +static void +gnome_canvas_clipgroup_init (GnomeCanvasClipgroup *clipgroup) +{ + clipgroup->path = NULL; + clipgroup->wind = ART_WIND_RULE_NONZERO; /* default winding rule */ + clipgroup->svp = NULL; +} + +static void +gnome_canvas_clipgroup_destroy (GtkObject *object) +{ + GnomeCanvasClipgroup *clipgroup; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_CLIPGROUP (object)); + + clipgroup = GNOME_CANVAS_CLIPGROUP (object); + + if (clipgroup->path) { + gnome_canvas_path_def_unref (clipgroup->path); + clipgroup->path = NULL; + } + + if (clipgroup->svp) { + art_svp_free (clipgroup->svp); + clipgroup->svp = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + + +static void +gnome_canvas_clipgroup_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasClipgroup *cgroup; + GnomeCanvasPathDef *gpp; + + item = GNOME_CANVAS_ITEM (object); + cgroup = GNOME_CANVAS_CLIPGROUP (object); + + switch (param_id) { + case PROP_PATH: + gpp = g_value_get_pointer (value); + + if (cgroup->path) { + gnome_canvas_path_def_unref (cgroup->path); + cgroup->path = NULL; + } + if (gpp != NULL) { + cgroup->path = gnome_canvas_path_def_closed_parts (gpp); + } + + gnome_canvas_item_request_update (item); + break; + + case PROP_WIND: + cgroup->wind = g_value_get_uint (value); + gnome_canvas_item_request_update (item); + break; + + default: + break; + } +} + +static void +gnome_canvas_clipgroup_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasClipgroup * cgroup; + + cgroup = GNOME_CANVAS_CLIPGROUP (object); + + switch (param_id) { + case PROP_PATH: + g_value_set_pointer (value, cgroup->path); + break; + + case PROP_WIND: + g_value_set_uint (value, cgroup->wind); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_clipgroup_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasClipgroup *clipgroup; + ArtSvpWriter *swr; + ArtBpath *bp; + ArtBpath *bpath; + ArtVpath *vpath; + ArtSVP *svp, *svp1, *svp2; + + clipgroup = GNOME_CANVAS_CLIPGROUP (item); + + if (clipgroup->svp) { + art_svp_free (clipgroup->svp); + clipgroup->svp = NULL; + } + + if (clipgroup->path) { + bp = gnome_canvas_path_def_bpath (clipgroup->path); + bpath = art_bpath_affine_transform (bp, affine); + + vpath = art_bez_path_to_vec (bpath, 0.25); + art_free (bpath); + + svp1 = art_svp_from_vpath (vpath); + art_free (vpath); + + swr = art_svp_writer_rewind_new (clipgroup->wind); + art_svp_intersector (svp1, swr); + + svp2 = art_svp_writer_rewind_reap (swr); + art_svp_free (svp1); + + if (clip_path != NULL) { + svp = art_svp_intersect (svp2, clip_path); + art_svp_free (svp2); + } else { + svp = svp2; + } + + clipgroup->svp = svp; + } + + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update) + (GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (item, affine, NULL, flags); + + if (clipgroup->svp) { + ArtDRect cbox; + art_drect_svp (&cbox, clipgroup->svp); + item->x1 = MAX (item->x1, cbox.x0 - 1.0); + item->y1 = MAX (item->y1, cbox.y0 - 1.0); + item->x2 = MIN (item->x2, cbox.x1 + 1.0); + item->y2 = MIN (item->y2, cbox.y1 + 1.0); + } +} + +/* non-premultiplied composition into RGB */ + +#define COMPOSEN11(fc,fa,bc) (((255 - (guint) (fa)) * (guint) (bc) + (guint) (fc) * (guint) (fa) + 127) / 255) + +static void +gnome_canvas_clipgroup_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) +{ + GnomeCanvasClipgroup *cg; + GnomeCanvasBuf lbuf; + guchar *mask; + + cg = GNOME_CANVAS_CLIPGROUP (item); + + if (cg->svp) { + gint bw, bh, sw, sh; + gint x, y; + + /* fixme: We could optimize background handling (lauris) */ + + if (buf->is_bg) { + gnome_canvas_buf_ensure_buf (buf); + buf->is_bg = FALSE; + buf->is_buf = TRUE; + } + + bw = buf->rect.x1 - buf->rect.x0; + bh = buf->rect.y1 - buf->rect.y0; + if ((bw < 1) || (bh < 1)) return; + + if (bw * bh <= GCG_BUF_PIXELS) { + /* We can go with single buffer */ + sw = bw; + sh = bh; + } else if (bw <= (GCG_BUF_PIXELS >> 3)) { + /* Go with row buffer */ + sw = bw; + sh = GCG_BUF_PIXELS / bw; + } else if (bh <= (GCG_BUF_PIXELS >> 3)) { + /* Go with column buffer */ + sw = GCG_BUF_PIXELS / bh; + sh = bh; + } else { + /* Tile buffer */ + sw = GCG_BUF_WIDTH; + sh = GCG_BUF_HEIGHT; + } + + /* Set up local buffer */ + lbuf.buf = gcg_buf_new (); + lbuf.bg_color = buf->bg_color; + lbuf.is_bg = FALSE; + lbuf.is_buf = TRUE; + /* Allocate mask */ + mask = gcg_mask_new (); + + for (y = buf->rect.y0; y < buf->rect.y1; y += sh) { + for (x = buf->rect.x0; x < buf->rect.x1; x += sw) { + gint r, xx, yy; + /* Set up local buffer */ + lbuf.rect.x0 = x; + lbuf.rect.y0 = y; + lbuf.rect.x1 = MIN (x + sw, buf->rect.x1); + lbuf.rect.y1 = MIN (y + sh, buf->rect.y1); + lbuf.buf_rowstride = 3 * (lbuf.rect.x1 - lbuf.rect.x0); + /* Copy background */ + for (r = lbuf.rect.y0; r < lbuf.rect.y1; r++) { + memcpy (lbuf.buf + (r - lbuf.rect.y0) * lbuf.buf_rowstride, + buf->buf + (r - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3, + (lbuf.rect.x1 - lbuf.rect.x0) * 3); + } + /* Invoke render method */ + if (((GnomeCanvasItemClass *) parent_class)->render) + ((GnomeCanvasItemClass *) parent_class)->render (item, &lbuf); + /* Render mask */ + art_gray_svp_aa (cg->svp, lbuf.rect.x0, lbuf.rect.y0, lbuf.rect.x1, lbuf.rect.y1, + mask, lbuf.rect.x1 - lbuf.rect.x0); + /* Combine */ + for (yy = lbuf.rect.y0; yy < lbuf.rect.y1; yy++) { + guchar *s, *m, *d; + s = lbuf.buf + (yy - lbuf.rect.y0) * lbuf.buf_rowstride; + m = mask + (yy - lbuf.rect.y0) * (lbuf.rect.x1 - lbuf.rect.x0); + d = buf->buf + (yy - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3; + for (xx = lbuf.rect.x0; xx < lbuf.rect.x1; xx++) { +#ifndef SHOW_SHADOW + d[0] = COMPOSEN11 (s[0], m[0], d[0]); + d[1] = COMPOSEN11 (s[1], m[0], d[1]); + d[2] = COMPOSEN11 (s[2], m[0], d[2]); +#else + d[0] = COMPOSEN11 (s[0], m[0] | 0x7f, d[0]); + d[1] = COMPOSEN11 (s[1], m[0] | 0x7f, d[1]); + d[2] = COMPOSEN11 (s[2], m[0] | 0x7f, d[2]); +#endif + s += 3; + m += 1; + d += 3; + } + } + } + } + /* Free buffers */ + gcg_mask_free (mask); + gcg_buf_free (lbuf.buf); + } else { + if (((GnomeCanvasItemClass *) parent_class)->render) + ((GnomeCanvasItemClass *) parent_class)->render (item, buf); + } +} + +static GSList *gcg_buffers = NULL; +static GSList *gcg_masks = NULL; + +static guchar * +gcg_buf_new (void) +{ + guchar *buf; + + if (!gcg_buffers) { + buf = g_new (guchar, GCG_BUF_SIZE); + } else { + buf = (guchar *) gcg_buffers->data; + gcg_buffers = g_slist_remove (gcg_buffers, buf); + } + + return buf; +} + +static void +gcg_buf_free (guchar *buf) +{ + gcg_buffers = g_slist_prepend (gcg_buffers, buf); +} + +static guchar * +gcg_mask_new (void) +{ + guchar *mask; + + if (!gcg_masks) { + mask = g_new (guchar, GCG_BUF_PIXELS); + } else { + mask = (guchar *) gcg_masks->data; + gcg_masks = g_slist_remove (gcg_masks, mask); + } + + return mask; +} + +static void +gcg_mask_free (guchar *mask) +{ + gcg_masks = g_slist_prepend (gcg_masks, mask); +} diff --git a/libgnomecanvas/gnome-canvas-clipgroup.h b/libgnomecanvas/gnome-canvas-clipgroup.h new file mode 100644 index 0000000000..e424497daa --- /dev/null +++ b/libgnomecanvas/gnome-canvas-clipgroup.h @@ -0,0 +1,58 @@ +#ifndef GNOME_CANVAS_CLIPGROUP_H +#define GNOME_CANVAS_CLIPGROUP_H + +/* Clipping group implementation for GnomeCanvas + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * TODO: Implement this in libgnomeui, possibly merge with real group + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Author: + * Lauris Kaplinski <lauris@ximian.com> + */ + +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-util.h> + +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_svp_wind.h> +#include <libart_lgpl/art_vpath_dash.h> +#include <libgnomecanvas/gnome-canvas-path-def.h> + +G_BEGIN_DECLS + + +#define GNOME_TYPE_CANVAS_CLIPGROUP (gnome_canvas_clipgroup_get_type ()) +#define GNOME_CANVAS_CLIPGROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_CLIPGROUP, GnomeCanvasClipgroup)) +#define GNOME_CANVAS_CLIPGROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_CLIPGROUP, GnomeCanvasClipgroupClass)) +#define GNOME_IS_CANVAS_CLIPGROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_CLIPGROUP)) +#define GNOME_IS_CANVAS_CLIPGROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_CLIPGROUP)) + + +typedef struct _GnomeCanvasClipgroup GnomeCanvasClipgroup; +typedef struct _GnomeCanvasClipgroupClass GnomeCanvasClipgroupClass; + +struct _GnomeCanvasClipgroup { + GnomeCanvasGroup group; + + GnomeCanvasPathDef * path; + ArtWindRule wind; + + ArtSVP * svp; +}; + +struct _GnomeCanvasClipgroupClass { + GnomeCanvasGroupClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_clipgroup_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-i18n.h b/libgnomecanvas/gnome-canvas-i18n.h new file mode 100644 index 0000000000..a768438c5f --- /dev/null +++ b/libgnomecanvas/gnome-canvas-i18n.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ + +/* + * Handles all of the internationalization configuration options. + * Author: Tom Tromey <tromey@creche.cygnus.com> + */ + +#ifndef __LIBGNOME_CANVAS_I18N_H__ +#define __LIBGNOME_CANVAS_I18N_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +#if !defined(__LIBGNOME_CANVAS_I18NP_H__) + +#ifdef ENABLE_NLS +# include <libintl.h> +# ifdef GNOME_EXPLICIT_TRANSLATION_DOMAIN +# undef _ +# define _(String) dgettext (GNOME_EXPLICIT_TRANSLATION_DOMAIN, String) +# else +# define _(String) gettext (String) +# endif +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +/* Stubs that do something close enough. */ +# define textdomain(String) (String) +# define gettext(String) (String) +# define dgettext(Domain,Message) (Message) +# define dcgettext(Domain,Message,Type) (Message) +# define bindtextdomain(Domain,Directory) (Domain) +# define _(String) (String) +# define N_(String) (String) +#endif + +#endif + +G_END_DECLS + +#endif /* __LIBGNOME_CANVAS_I18N_H__ */ diff --git a/libgnomecanvas/gnome-canvas-line.c b/libgnomecanvas/gnome-canvas-line.c new file mode 100644 index 0000000000..48cafb71c7 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-line.c @@ -0,0 +1,1423 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ + +/* Line/curve item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#include <config.h> +#include <math.h> +#include <string.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> +#include "libgnomecanvas.h" + +#define noVERBOSE + +#define DEFAULT_SPLINE_STEPS 12 /* this is what Tk uses */ +#define NUM_ARROW_POINTS 6 /* number of points in an arrowhead */ +#define NUM_STATIC_POINTS 256 /* number of static points to use to avoid allocating arrays */ + + +#define GROW_BOUNDS(bx1, by1, bx2, by2, x, y) { \ + if (x < bx1) \ + bx1 = x; \ + \ + if (x > bx2) \ + bx2 = x; \ + \ + if (y < by1) \ + by1 = y; \ + \ + if (y > by2) \ + by2 = y; \ +} + + +enum { + PROP_0, + PROP_POINTS, + PROP_FILL_COLOR, + PROP_FILL_COLOR_GDK, + PROP_FILL_COLOR_RGBA, + PROP_FILL_STIPPLE, + PROP_WIDTH_PIXELS, + PROP_WIDTH_UNITS, + PROP_CAP_STYLE, + PROP_JOIN_STYLE, + PROP_LINE_STYLE, + PROP_FIRST_ARROWHEAD, + PROP_LAST_ARROWHEAD, + PROP_SMOOTH, + PROP_SPLINE_STEPS, + PROP_ARROW_SHAPE_A, + PROP_ARROW_SHAPE_B, + PROP_ARROW_SHAPE_C +}; + + +static void gnome_canvas_line_class_init (GnomeCanvasLineClass *class); +static void gnome_canvas_line_init (GnomeCanvasLine *line); +static void gnome_canvas_line_destroy (GtkObject *object); +static void gnome_canvas_line_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_line_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_line_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static void gnome_canvas_line_realize (GnomeCanvasItem *item); +static void gnome_canvas_line_unrealize (GnomeCanvasItem *item); +static void gnome_canvas_line_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); +static double gnome_canvas_line_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item); +static void gnome_canvas_line_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); +static void gnome_canvas_line_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); + + +static GnomeCanvasItemClass *parent_class; + + +GType +gnome_canvas_line_get_type (void) +{ + static GType line_type; + + if (!line_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasLineClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_line_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasLine), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_line_init, + NULL /* value_table */ + }; + + line_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasLine", + &object_info, 0); + } + + return line_type; +} + +static void +gnome_canvas_line_class_init (GnomeCanvasLineClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_line_set_property; + gobject_class->get_property = gnome_canvas_line_get_property; + + g_object_class_install_property + (gobject_class, + PROP_POINTS, + g_param_spec_boxed ("points", NULL, NULL, + GNOME_TYPE_CANVAS_POINTS, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR, + g_param_spec_string ("fill_color", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR_GDK, + g_param_spec_boxed ("fill_color_gdk", NULL, NULL, + GDK_TYPE_COLOR, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR_RGBA, + g_param_spec_uint ("fill_color_rgba", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_STIPPLE, + g_param_spec_object ("fill_stipple", NULL, NULL, + GDK_TYPE_DRAWABLE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH_PIXELS, + g_param_spec_uint ("width_pixels", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH_UNITS, + g_param_spec_double ("width_units", NULL, NULL, + 0.0, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_CAP_STYLE, + g_param_spec_enum ("cap_style", NULL, NULL, + GDK_TYPE_CAP_STYLE, + GDK_CAP_BUTT, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_JOIN_STYLE, + g_param_spec_enum ("join_style", NULL, NULL, + GDK_TYPE_JOIN_STYLE, + GDK_JOIN_MITER, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_LINE_STYLE, + g_param_spec_enum ("line_style", NULL, NULL, + GDK_TYPE_LINE_STYLE, + GDK_LINE_SOLID, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FIRST_ARROWHEAD, + g_param_spec_boolean ("first_arrowhead", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_LAST_ARROWHEAD, + g_param_spec_boolean ("last_arrowhead", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_SMOOTH, + g_param_spec_boolean ("smooth", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_SPLINE_STEPS, + g_param_spec_uint ("spline_steps", NULL, NULL, + 0, G_MAXUINT, DEFAULT_SPLINE_STEPS, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_ARROW_SHAPE_A, + g_param_spec_double ("arrow_shape_a", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_ARROW_SHAPE_B, + g_param_spec_double ("arrow_shape_b", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_ARROW_SHAPE_C, + g_param_spec_double ("arrow_shape_c", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_line_destroy; + + item_class->update = gnome_canvas_line_update; + item_class->realize = gnome_canvas_line_realize; + item_class->unrealize = gnome_canvas_line_unrealize; + item_class->draw = gnome_canvas_line_draw; + item_class->point = gnome_canvas_line_point; + item_class->bounds = gnome_canvas_line_bounds; + + item_class->render = gnome_canvas_line_render; +} + +static void +gnome_canvas_line_init (GnomeCanvasLine *line) +{ + line->width = 0.0; + line->cap = GDK_CAP_BUTT; + line->join = GDK_JOIN_MITER; + line->line_style = GDK_LINE_SOLID; + line->shape_a = 0.0; + line->shape_b = 0.0; + line->shape_c = 0.0; + line->spline_steps = DEFAULT_SPLINE_STEPS; +} + +static void +gnome_canvas_line_destroy (GtkObject *object) +{ + GnomeCanvasLine *line; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_LINE (object)); + + line = GNOME_CANVAS_LINE (object); + + /* remember, destroy can be run multiple times! */ + + if (line->coords) + g_free (line->coords); + line->coords = NULL; + + if (line->first_coords) + g_free (line->first_coords); + line->first_coords = NULL; + + if (line->last_coords) + g_free (line->last_coords); + line->last_coords = NULL; + + if (line->stipple) + g_object_unref (line->stipple); + line->stipple = NULL; + + if (line->fill_svp) + art_svp_free (line->fill_svp); + line->fill_svp = NULL; + + if (line->first_svp) + art_svp_free (line->first_svp); + line->first_svp = NULL; + + if (line->last_svp) + art_svp_free (line->last_svp); + line->last_svp = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +/* Computes the bounding box of the line, including its arrow points. Assumes that the number of + * points in the line is not zero. + */ +static void +get_bounds (GnomeCanvasLine *line, double *bx1, double *by1, double *bx2, double *by2) +{ + double *coords; + double x1, y1, x2, y2; + double width; + int i; + + if (!line->coords) { + *bx1 = *by1 = *bx2 = *by2 = 0.0; + return; + } + + /* Find bounding box of line's points */ + + x1 = x2 = line->coords[0]; + y1 = y2 = line->coords[1]; + + for (i = 1, coords = line->coords + 2; i < line->num_points; i++, coords += 2) + GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]); + + /* Add possible over-estimate for wide lines */ + + if (line->width_pixels) + width = line->width / line->item.canvas->pixels_per_unit; + else + width = line->width; + + x1 -= width; + y1 -= width; + x2 += width; + y2 += width; + + /* For mitered lines, make a second pass through all the points. Compute the location of + * the two miter vertex points and add them to the bounding box. + */ + + if (line->join == GDK_JOIN_MITER) + for (i = line->num_points, coords = line->coords; i >= 3; i--, coords += 2) { + double mx1, my1, mx2, my2; + + if (gnome_canvas_get_miter_points (coords[0], coords[1], + coords[2], coords[3], + coords[4], coords[5], + width, + &mx1, &my1, &mx2, &my2)) { + GROW_BOUNDS (x1, y1, x2, y2, mx1, my1); + GROW_BOUNDS (x1, y1, x2, y2, mx2, my2); + } + } + + /* Add the arrow points, if any */ + + if (line->first_arrow && line->first_coords) + for (i = 0, coords = line->first_coords; i < NUM_ARROW_POINTS; i++, coords += 2) + GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]); + + if (line->last_arrow && line->last_coords) + for (i = 0, coords = line->last_coords; i < NUM_ARROW_POINTS; i++, coords += 2) + GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]); + + /* Done */ + + *bx1 = x1; + *by1 = y1; + *bx2 = x2; + *by2 = y2; +} + +/* Computes the bounding box of the line, in canvas coordinates. Assumes that the number of points in the polygon is + * not zero. Affine is the i2c transformation. + */ +static void +get_bounds_canvas (GnomeCanvasLine *line, double *bx1, double *by1, double *bx2, double *by2, double affine[6]) +{ + /* It would be possible to tighten the bounds somewhat by transforming the individual points before + aggregating them into the bbox. But it hardly seems worth it. */ + ArtDRect bbox_world; + ArtDRect bbox_canvas; + + get_bounds (line, &bbox_world.x0, &bbox_world.y0, &bbox_world.x1, &bbox_world.y1); + + art_drect_affine_transform (&bbox_canvas, &bbox_world, affine); + /* include 1 pixel of fudge */ + *bx1 = bbox_canvas.x0 - 1; + *by1 = bbox_canvas.y0 - 1; + *bx2 = bbox_canvas.x1 + 1; + *by2 = bbox_canvas.y1 + 1; +} + +/* Recalculates the arrow polygons for the line */ +static void +reconfigure_arrows (GnomeCanvasLine *line) +{ + double *poly, *coords; + double dx, dy, length; + double sin_theta, cos_theta, tmp; + double frac_height; /* Line width as fraction of arrowhead width */ + double backup; /* Distance to backup end points so the line ends in the middle of the arrowhead */ + double vx, vy; /* Position of arrowhead vertex */ + double shape_a, shape_b, shape_c; + double width; + int i; + + if (line->num_points == 0) + return; + + /* Set up things */ + + if (line->first_arrow) { + if (line->first_coords) { + line->coords[0] = line->first_coords[0]; + line->coords[1] = line->first_coords[1]; + } else + line->first_coords = g_new (double, 2 * NUM_ARROW_POINTS); + } else if (line->first_coords) { + line->coords[0] = line->first_coords[0]; + line->coords[1] = line->first_coords[1]; + + g_free (line->first_coords); + line->first_coords = NULL; + } + + i = 2 * (line->num_points - 1); + + if (line->last_arrow) { + if (line->last_coords) { + line->coords[i] = line->last_coords[0]; + line->coords[i + 1] = line->last_coords[1]; + } else + line->last_coords = g_new (double, 2 * NUM_ARROW_POINTS); + } else if (line->last_coords) { + line->coords[i] = line->last_coords[0]; + line->coords[i + 1] = line->last_coords[1]; + + g_free (line->last_coords); + line->last_coords = NULL; + } + + if (!line->first_arrow && !line->last_arrow) + return; + + if (line->width_pixels) + width = line->width / line->item.canvas->pixels_per_unit; + else + width = line->width; + + /* Add fudge value for better-looking results */ + + shape_a = line->shape_a; + shape_b = line->shape_b; + shape_c = line->shape_c + width / 2.0; + + if (line->width_pixels) { + shape_a /= line->item.canvas->pixels_per_unit; + shape_b /= line->item.canvas->pixels_per_unit; + shape_c /= line->item.canvas->pixels_per_unit; + } + + shape_a += 0.001; + shape_b += 0.001; + shape_c += 0.001; + + /* Compute the polygon for the first arrowhead and adjust the first point in the line so + * that the line does not stick out past the leading edge of the arrowhead. + */ + + frac_height = (line->width / 2.0) / shape_c; + backup = frac_height * shape_b + shape_a * (1.0 - frac_height) / 2.0; + + if (line->first_arrow) { + poly = line->first_coords; + poly[0] = poly[10] = line->coords[0]; + poly[1] = poly[11] = line->coords[1]; + + dx = poly[0] - line->coords[2]; + dy = poly[1] - line->coords[3]; + length = sqrt (dx * dx + dy * dy); + if (length < GNOME_CANVAS_EPSILON) + sin_theta = cos_theta = 0.0; + else { + sin_theta = dy / length; + cos_theta = dx / length; + } + + vx = poly[0] - shape_a * cos_theta; + vy = poly[1] - shape_a * sin_theta; + + tmp = shape_c * sin_theta; + + poly[2] = poly[0] - shape_b * cos_theta + tmp; + poly[8] = poly[2] - 2.0 * tmp; + + tmp = shape_c * cos_theta; + + poly[3] = poly[1] - shape_b * sin_theta - tmp; + poly[9] = poly[3] + 2.0 * tmp; + + poly[4] = poly[2] * frac_height + vx * (1.0 - frac_height); + poly[5] = poly[3] * frac_height + vy * (1.0 - frac_height); + poly[6] = poly[8] * frac_height + vx * (1.0 - frac_height); + poly[7] = poly[9] * frac_height + vy * (1.0 - frac_height); + + /* Move the first point towards the second so that the corners at the end of the + * line are inside the arrowhead. + */ + + line->coords[0] = poly[0] - backup * cos_theta; + line->coords[1] = poly[1] - backup * sin_theta; + } + + /* Same process for last arrowhead */ + + if (line->last_arrow) { + coords = line->coords + 2 * (line->num_points - 2); + poly = line->last_coords; + poly[0] = poly[10] = coords[2]; + poly[1] = poly[11] = coords[3]; + + dx = poly[0] - coords[0]; + dy = poly[1] - coords[1]; + length = sqrt (dx * dx + dy * dy); + if (length < GNOME_CANVAS_EPSILON) + sin_theta = cos_theta = 0.0; + else { + sin_theta = dy / length; + cos_theta = dx / length; + } + + vx = poly[0] - shape_a * cos_theta; + vy = poly[1] - shape_a * sin_theta; + + tmp = shape_c * sin_theta; + + poly[2] = poly[0] - shape_b * cos_theta + tmp; + poly[8] = poly[2] - 2.0 * tmp; + + tmp = shape_c * cos_theta; + + poly[3] = poly[1] - shape_b * sin_theta - tmp; + poly[9] = poly[3] + 2.0 * tmp; + + poly[4] = poly[2] * frac_height + vx * (1.0 - frac_height); + poly[5] = poly[3] * frac_height + vy * (1.0 - frac_height); + poly[6] = poly[8] * frac_height + vx * (1.0 - frac_height); + poly[7] = poly[9] * frac_height + vy * (1.0 - frac_height); + + coords[2] = poly[0] - backup * cos_theta; + coords[3] = poly[1] - backup * sin_theta; + } +} + +/* Convenience function to set the line's GC's foreground color */ +static void +set_line_gc_foreground (GnomeCanvasLine *line) +{ + GdkColor c; + + if (!line->gc) + return; + + c.pixel = line->fill_pixel; + gdk_gc_set_foreground (line->gc, &c); +} + +/* Recalculate the line's width and set it in its GC */ +static void +set_line_gc_width (GnomeCanvasLine *line) +{ + int width; + + if (!line->gc) + return; + + if (line->width_pixels) + width = (int) line->width; + else + width = (int) (line->width * line->item.canvas->pixels_per_unit + 0.5); + + gdk_gc_set_line_attributes (line->gc, + width, + line->line_style, + (line->first_arrow || line->last_arrow) ? GDK_CAP_BUTT : line->cap, + line->join); +} + +/* Sets the stipple pattern for the line */ +static void +set_stipple (GnomeCanvasLine *line, GdkBitmap *stipple, int reconfigure) +{ + if (line->stipple && !reconfigure) + g_object_unref (line->stipple); + + line->stipple = stipple; + if (stipple && !reconfigure) + g_object_ref (stipple); + + if (line->gc) { + if (stipple) { + gdk_gc_set_stipple (line->gc, stipple); + gdk_gc_set_fill (line->gc, GDK_STIPPLED); + } else + gdk_gc_set_fill (line->gc, GDK_SOLID); + } +} + +static void +gnome_canvas_line_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasLine *line; + GnomeCanvasPoints *points; + GdkColor color = { 0, 0, 0, 0, }; + GdkColor *pcolor; + gboolean color_changed; + int have_pixel; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_LINE (object)); + + item = GNOME_CANVAS_ITEM (object); + line = GNOME_CANVAS_LINE (object); + + color_changed = FALSE; + have_pixel = FALSE; + + switch (param_id) { + case PROP_POINTS: + points = g_value_get_boxed (value); + + if (line->coords) { + g_free (line->coords); + line->coords = NULL; + } + + if (!points) + line->num_points = 0; + else { + line->num_points = points->num_points; + line->coords = g_new (double, 2 * line->num_points); + memcpy (line->coords, points->coords, 2 * line->num_points * sizeof (double)); + } + + /* Drop the arrowhead polygons if they exist -- they will be regenerated */ + + if (line->first_coords) { + g_free (line->first_coords); + line->first_coords = NULL; + } + + if (line->last_coords) { + g_free (line->last_coords); + line->last_coords = NULL; + } + + /* Since the line's points have changed, we need to re-generate arrowheads in + * addition to recalculating the bounds. + */ + gnome_canvas_item_request_update (item); + break; + + case PROP_FILL_COLOR: + if (g_value_get_string (value)) + gdk_color_parse (g_value_get_string (value), &color); + line->fill_rgba = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + color_changed = TRUE; + break; + + case PROP_FILL_COLOR_GDK: + pcolor = g_value_get_boxed (value); + if (pcolor) { + GdkColormap *colormap; + color = *pcolor; + + colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas)); + gdk_rgb_find_color (colormap, &color); + + have_pixel = TRUE; + } + + line->fill_rgba = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + color_changed = TRUE; + break; + + case PROP_FILL_COLOR_RGBA: + line->fill_rgba = g_value_get_uint (value); + color_changed = TRUE; + break; + + case PROP_FILL_STIPPLE: + set_stipple (line, (GdkBitmap *) g_value_get_object (value), FALSE); + gnome_canvas_item_request_redraw_svp (item, line->fill_svp); + break; + + case PROP_WIDTH_PIXELS: + line->width = g_value_get_uint (value); + line->width_pixels = TRUE; + set_line_gc_width (line); + gnome_canvas_item_request_update (item); + break; + + case PROP_WIDTH_UNITS: + line->width = fabs (g_value_get_double (value)); + line->width_pixels = FALSE; + set_line_gc_width (line); + gnome_canvas_item_request_update (item); + break; + + case PROP_CAP_STYLE: + line->cap = g_value_get_enum (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_JOIN_STYLE: + line->join = g_value_get_enum (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_LINE_STYLE: + line->line_style = g_value_get_enum (value); + set_line_gc_width (line); + gnome_canvas_item_request_update (item); + break; + + case PROP_FIRST_ARROWHEAD: + line->first_arrow = g_value_get_boolean (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_LAST_ARROWHEAD: + line->last_arrow = g_value_get_boolean (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_SMOOTH: + /* FIXME */ + break; + + case PROP_SPLINE_STEPS: + /* FIXME */ + break; + + case PROP_ARROW_SHAPE_A: + line->shape_a = fabs (g_value_get_double (value)); + gnome_canvas_item_request_update (item); + break; + + case PROP_ARROW_SHAPE_B: + line->shape_b = fabs (g_value_get_double (value)); + gnome_canvas_item_request_update (item); + break; + + case PROP_ARROW_SHAPE_C: + line->shape_c = fabs (g_value_get_double (value)); + gnome_canvas_item_request_update (item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + if (color_changed) { + if (have_pixel) + line->fill_pixel = color.pixel; + else + line->fill_pixel = gnome_canvas_get_color_pixel (item->canvas, + line->fill_rgba); + + if (!item->canvas->aa) + set_line_gc_foreground (line); + + gnome_canvas_item_request_redraw_svp (item, line->fill_svp); + + if (line->first_svp) + gnome_canvas_item_request_redraw_svp (item, line->first_svp); + + if (line->last_svp) + gnome_canvas_item_request_redraw_svp (item, line->last_svp); + + } +} + +/* Returns a copy of the line's points without the endpoint adjustments for + * arrowheads. + */ +static GnomeCanvasPoints * +get_points (GnomeCanvasLine *line) +{ + GnomeCanvasPoints *points; + int start_ofs, end_ofs; + + if (line->num_points == 0) + return NULL; + + start_ofs = end_ofs = 0; + + points = gnome_canvas_points_new (line->num_points); + + /* Invariant: if first_coords or last_coords exist, then the line's + * endpoints have been adjusted. + */ + + if (line->first_coords) { + start_ofs = 1; + + points->coords[0] = line->first_coords[0]; + points->coords[1] = line->first_coords[1]; + } + + if (line->last_coords) { + end_ofs = 1; + + points->coords[2 * (line->num_points - 1)] = line->last_coords[0]; + points->coords[2 * (line->num_points - 1) + 1] = line->last_coords[1]; + } + + memcpy (points->coords + 2 * start_ofs, + line->coords + 2 * start_ofs, + 2 * (line->num_points - (start_ofs + end_ofs)) * sizeof (double)); + + return points; +} + +static void +gnome_canvas_line_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasLine *line; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_LINE (object)); + + line = GNOME_CANVAS_LINE (object); + + switch (param_id) { + case PROP_POINTS: + /* get_points returns a copy */ + g_value_take_boxed (value, get_points (line)); + break; + + case PROP_FILL_COLOR: + g_value_take_string (value, + g_strdup_printf ("#%02x%02x%02x", + line->fill_rgba >> 24, + (line->fill_rgba >> 16) & 0xff, + (line->fill_rgba >> 8) & 0xff)); + break; + + case PROP_FILL_COLOR_GDK: { + GnomeCanvas *canvas = GNOME_CANVAS_ITEM (line)->canvas; + GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas)); + GdkColor color; + + gdk_colormap_query_color (colormap, line->fill_pixel, &color); + g_value_set_boxed (value, &color); + break; + } + + case PROP_FILL_COLOR_RGBA: + g_value_set_uint (value, line->fill_rgba); + break; + + case PROP_FILL_STIPPLE: + g_value_set_object (value, line->stipple); + break; + + case PROP_WIDTH_PIXELS: + g_value_set_uint (value, line->width); + break; + + case PROP_WIDTH_UNITS: + g_value_set_double (value, line->width); + break; + + case PROP_CAP_STYLE: + g_value_set_enum (value, line->cap); + break; + + case PROP_JOIN_STYLE: + g_value_set_enum (value, line->join); + break; + + case PROP_LINE_STYLE: + g_value_set_enum (value, line->line_style); + break; + + case PROP_FIRST_ARROWHEAD: + g_value_set_boolean (value, line->first_arrow); + break; + + case PROP_LAST_ARROWHEAD: + g_value_set_boolean (value, line->last_arrow); + break; + + case PROP_SMOOTH: + g_value_set_boolean (value, line->smooth); + break; + + case PROP_SPLINE_STEPS: + g_value_set_uint (value, line->spline_steps); + break; + + case PROP_ARROW_SHAPE_A: + g_value_set_double (value, line->shape_a); + break; + + case PROP_ARROW_SHAPE_B: + g_value_set_double (value, line->shape_b); + break; + + case PROP_ARROW_SHAPE_C: + g_value_set_double (value, line->shape_c); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_line_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf) +{ + GnomeCanvasLine *line; + + line = GNOME_CANVAS_LINE (item); + + if (line->fill_svp != NULL) + gnome_canvas_render_svp (buf, line->fill_svp, line->fill_rgba); + + if (line->first_svp != NULL) + gnome_canvas_render_svp (buf, line->first_svp, line->fill_rgba); + + if (line->last_svp != NULL) + gnome_canvas_render_svp (buf, line->last_svp, line->fill_rgba); +} + + +static ArtSVP * +svp_from_points (const double *item_coords, int num_points, const double affine[6]) +{ + ArtVpath *vpath; + ArtSVP *svp; + double x, y; + int i; + + vpath = art_new (ArtVpath, num_points + 2); + + for (i = 0; i < num_points; i++) { + vpath[i].code = i == 0 ? ART_MOVETO : ART_LINETO; + x = item_coords[i * 2]; + y = item_coords[i * 2 + 1]; + vpath[i].x = x * affine[0] + y * affine[2] + affine[4]; + vpath[i].y = x * affine[1] + y * affine[3] + affine[5]; + } +#if 0 + vpath[i].code = ART_LINETO; + vpath[i].x = vpath[0].x; + vpath[i].y = vpath[0].y; + i++; +#endif + vpath[i].code = ART_END; + vpath[i].x = 0; + vpath[i].y = 0; + + svp = art_svp_from_vpath (vpath); + + art_free (vpath); + + return svp; +} + +static void +gnome_canvas_line_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasLine *line; + int i; + ArtVpath *vpath; + ArtPoint pi, pc; + double width; + ArtSVP *svp; + double x1, y1, x2, y2; + + line = GNOME_CANVAS_LINE (item); + + if (parent_class->update) + (* parent_class->update) (item, affine, clip_path, flags); + + reconfigure_arrows (line); + + if (item->canvas->aa) { + gnome_canvas_item_reset_bounds (item); + + vpath = art_new (ArtVpath, line->num_points + 2); + + for (i = 0; i < line->num_points; i++) { + pi.x = line->coords[i * 2]; + pi.y = line->coords[i * 2 + 1]; + art_affine_point (&pc, &pi, affine); + vpath[i].code = i == 0 ? ART_MOVETO : ART_LINETO; + vpath[i].x = pc.x; + vpath[i].y = pc.y; + } + vpath[i].code = ART_END; + vpath[i].x = 0; + vpath[i].y = 0; + + if (line->width_pixels) + width = line->width; + else + width = line->width * art_affine_expansion (affine); + + if (width < 0.5) + width = 0.5; + + svp = art_svp_vpath_stroke (vpath, + gnome_canvas_join_gdk_to_art (line->join), + gnome_canvas_cap_gdk_to_art (line->cap), + width, + 4, + 0.25); + art_free (vpath); + + gnome_canvas_item_update_svp_clip (item, &line->fill_svp, svp, clip_path); + + if (line->first_arrow && line->first_coords) { + svp = svp_from_points (line->first_coords, NUM_ARROW_POINTS, affine); + gnome_canvas_item_update_svp_clip (item, + &line->first_svp, svp, clip_path); + } + + + if (line->last_arrow && line->last_coords) { + svp = svp_from_points (line->last_coords, NUM_ARROW_POINTS, affine); + gnome_canvas_item_update_svp_clip (item, + &line->last_svp, svp, clip_path); + } + + + } else { + set_line_gc_foreground (line); + set_line_gc_width (line); + set_stipple (line, line->stipple, TRUE); + + get_bounds_canvas (line, &x1, &y1, &x2, &y2, affine); + gnome_canvas_update_bbox (item, x1, y1, x2, y2); + } +} + +static void +gnome_canvas_line_realize (GnomeCanvasItem *item) +{ + GnomeCanvasLine *line; + + line = GNOME_CANVAS_LINE (item); + + if (parent_class->realize) + (* parent_class->realize) (item); + + line->gc = gdk_gc_new (item->canvas->layout.bin_window); + +#if 0 + (* GNOME_CANVAS_ITEM_CLASS (item->object.klass)->update) (item, NULL, NULL, 0); +#endif +} + +static void +gnome_canvas_line_unrealize (GnomeCanvasItem *item) +{ + GnomeCanvasLine *line; + + line = GNOME_CANVAS_LINE (item); + + g_object_unref (line->gc); + line->gc = NULL; + + if (parent_class->unrealize) + (* parent_class->unrealize) (item); +} + +static void +item_to_canvas (GnomeCanvas *canvas, double *item_coords, GdkPoint *canvas_coords, int num_points, + int *num_drawn_points, double i2c[6], int x, int y) +{ + int i; + int old_cx, old_cy; + int cx, cy; + ArtPoint pi, pc; + +#ifdef VERBOSE + { + char str[128]; + art_affine_to_string (str, i2c); + g_print ("line item_to_canvas %s\n", str); + } +#endif + + /* the first point is always drawn */ + + pi.x = item_coords[0]; + pi.y = item_coords[1]; + art_affine_point (&pc, &pi, i2c); + cx = floor (pc.x + 0.5); + cy = floor (pc.y + 0.5); + canvas_coords->x = cx - x; + canvas_coords->y = cy - y; + canvas_coords++; + old_cx = cx; + old_cy = cy; + *num_drawn_points = 1; + + for (i = 1; i < num_points; i++) { + pi.x = item_coords[i * 2]; + pi.y = item_coords[i * 2 + 1]; + art_affine_point (&pc, &pi, i2c); + cx = floor (pc.x + 0.5); + cy = floor (pc.y + 0.5); + if (old_cx != cx || old_cy != cy) { + canvas_coords->x = cx - x; + canvas_coords->y = cy - y; + old_cx = cx; + old_cy = cy; + canvas_coords++; + (*num_drawn_points)++; + } + } +} + +static void +gnome_canvas_line_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + GnomeCanvasLine *line; + GdkPoint static_points[NUM_STATIC_POINTS]; + GdkPoint *points; + int actual_num_points_drawn; + double i2c[6]; + + line = GNOME_CANVAS_LINE (item); + + if (line->num_points == 0) + return; + + /* Build array of canvas pixel coordinates */ + + if (line->num_points <= NUM_STATIC_POINTS) + points = static_points; + else + points = g_new (GdkPoint, line->num_points); + + + gnome_canvas_item_i2c_affine (item, i2c); + + item_to_canvas (item->canvas, line->coords, points, line->num_points, + &actual_num_points_drawn, i2c, x, y); + + if (line->stipple) + gnome_canvas_set_stipple_origin (item->canvas, line->gc); + + gdk_draw_lines (drawable, line->gc, points, actual_num_points_drawn); + + if (points != static_points) + g_free (points); + + /* Draw arrowheads */ + + points = static_points; + + if (line->first_arrow) { + item_to_canvas (item->canvas, line->first_coords, points, NUM_ARROW_POINTS, + &actual_num_points_drawn, i2c, x, y); + gdk_draw_polygon (drawable, line->gc, TRUE, points, actual_num_points_drawn ); + } + + if (line->last_arrow) { + item_to_canvas (item->canvas, line->last_coords, points, NUM_ARROW_POINTS, + &actual_num_points_drawn, i2c, x, y); + gdk_draw_polygon (drawable, line->gc, TRUE, points, actual_num_points_drawn ); + } +} + +static double +gnome_canvas_line_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item) +{ + GnomeCanvasLine *line; + double *line_points = NULL, *coords; + double static_points[2 * NUM_STATIC_POINTS]; + double poly[10]; + double best, dist; + double dx, dy; + double width; + int num_points = 0, i; + int changed_miter_to_bevel; + +#ifdef VERBOSE + g_print ("gnome_canvas_line_point x, y = (%g, %g); cx, cy = (%d, %d)\n", x, y, cx, cy); +#endif + + line = GNOME_CANVAS_LINE (item); + + *actual_item = item; + + best = 1.0e36; + + /* Handle smoothed lines by generating an expanded set ot points */ + + if (line->smooth && (line->num_points > 2)) { + /* FIXME */ + } else { + num_points = line->num_points; + line_points = line->coords; + } + + /* Compute a polygon for each edge of the line and test the point against it. The effective + * width of the line is adjusted so that it will be at least one pixel thick (so that zero + * pixel-wide lines can be pickedup as well). + */ + + if (line->width_pixels) + width = line->width / item->canvas->pixels_per_unit; + else + width = line->width; + + if (width < (1.0 / item->canvas->pixels_per_unit)) + width = 1.0 / item->canvas->pixels_per_unit; + + changed_miter_to_bevel = 0; + + for (i = num_points, coords = line_points; i >= 2; i--, coords += 2) { + /* If rounding is done around the first point, then compute distance between the + * point and the first point. + */ + + if (((line->cap == GDK_CAP_ROUND) && (i == num_points)) + || ((line->join == GDK_JOIN_ROUND) && (i != num_points))) { + dx = coords[0] - x; + dy = coords[1] - y; + dist = sqrt (dx * dx + dy * dy) - width / 2.0; + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else if (dist < best) + best = dist; + } + + /* Compute the polygonal shape corresponding to this edge, with two points for the + * first point of the edge and two points for the last point of the edge. + */ + + if (i == num_points) + gnome_canvas_get_butt_points (coords[2], coords[3], coords[0], coords[1], + width, (line->cap == GDK_CAP_PROJECTING), + poly, poly + 1, poly + 2, poly + 3); + else if ((line->join == GDK_JOIN_MITER) && !changed_miter_to_bevel) { + poly[0] = poly[6]; + poly[1] = poly[7]; + poly[2] = poly[4]; + poly[3] = poly[5]; + } else { + gnome_canvas_get_butt_points (coords[2], coords[3], coords[0], coords[1], + width, FALSE, + poly, poly + 1, poly + 2, poly + 3); + + /* If this line uses beveled joints, then check the distance to a polygon + * comprising the last two points of the previous polygon and the first two + * from this polygon; this checks the wedges that fill the mitered point. + */ + + if ((line->join == GDK_JOIN_BEVEL) || changed_miter_to_bevel) { + poly[8] = poly[0]; + poly[9] = poly[1]; + + dist = gnome_canvas_polygon_to_point (poly, 5, x, y); + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else if (dist < best) + best = dist; + + changed_miter_to_bevel = FALSE; + } + } + + if (i == 2) + gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3], + width, (line->cap == GDK_CAP_PROJECTING), + poly + 4, poly + 5, poly + 6, poly + 7); + else if (line->join == GDK_JOIN_MITER) { + if (!gnome_canvas_get_miter_points (coords[0], coords[1], + coords[2], coords[3], + coords[4], coords[5], + width, + poly + 4, poly + 5, poly + 6, poly + 7)) { + changed_miter_to_bevel = TRUE; + gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3], + width, FALSE, + poly + 4, poly + 5, poly + 6, poly + 7); + } + } else + gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3], + width, FALSE, + poly + 4, poly + 5, poly + 6, poly + 7); + + poly[8] = poly[0]; + poly[9] = poly[1]; + + dist = gnome_canvas_polygon_to_point (poly, 5, x, y); + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else if (dist < best) + best = dist; + } + + /* If caps are rounded, check the distance to the cap around the final end point of the line */ + + if (line->cap == GDK_CAP_ROUND) { + dx = coords[0] - x; + dy = coords[1] - y; + dist = sqrt (dx * dx + dy * dy) - width / 2.0; + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else + best = dist; + } + + /* sometimes the GnomeCanvasItem::update signal will not have + been processed between deleting the arrow points and a call + to this routine -- this can cause a segfault here */ + if ((line->first_arrow && !line->first_coords) || + (line->last_arrow && !line->last_coords)) + reconfigure_arrows(line); + + /* If there are arrowheads, check the distance to them */ + + if (line->first_arrow) { + dist = gnome_canvas_polygon_to_point (line->first_coords, NUM_ARROW_POINTS, x, y); + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else + best = dist; + } + + if (line->last_arrow) { + dist = gnome_canvas_polygon_to_point (line->last_coords, NUM_ARROW_POINTS, x, y); + if (dist < GNOME_CANVAS_EPSILON) { + best = 0.0; + goto done; + } else + best = dist; + } + +done: + + if ((line_points != static_points) && (line_points != line->coords)) + g_free (line_points); + + return best; +} + +static void +gnome_canvas_line_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasLine *line; + + line = GNOME_CANVAS_LINE (item); + + if (line->num_points == 0) { + *x1 = *y1 = *x2 = *y2 = 0.0; + return; + } + + get_bounds (line, x1, y1, x2, y2); +} diff --git a/libgnomecanvas/gnome-canvas-line.h b/libgnomecanvas/gnome-canvas-line.h new file mode 100644 index 0000000000..4ab6cfa62d --- /dev/null +++ b/libgnomecanvas/gnome-canvas-line.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ + +/* Line/curve item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#ifndef GNOME_CANVAS_LINE_H +#define GNOME_CANVAS_LINE_H + + +#include <libgnomecanvas/gnome-canvas.h> + + +G_BEGIN_DECLS + + +/* Line item for the canvas. This is a polyline with configurable width, cap/join styles, and arrowheads. + * If arrowheads are enabled, then three values are used to specify their shape: + * + * arrow_shape_a: Distance from tip of arrowhead to the center point. + * arrow_shape_b: Distance from tip of arrowhead to trailing point, measured along the shaft. + * arrow_shape_c: Distance of trailing point from outside edge of shaft. + * + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * points GnomeCanvasPoints* RW Pointer to a GnomeCanvasPoints structure. + * This can be created by a call to + * gnome_canvas_points_new() (in gnome-canvas-util.h). + * X coordinates are in the even indices of the + * points->coords array, Y coordinates are in + * the odd indices. + * fill_color string W X color specification for line + * fill_color_gdk GdkColor* RW Pointer to an allocated GdkColor + * fill_stipple GdkBitmap* RW Stipple pattern for the line + * width_pixels uint R Width of the line in pixels. The line width + * will not be scaled when the canvas zoom factor changes. + * width_units double R Width of the line in canvas units. The line width + * will be scaled when the canvas zoom factor changes. + * cap_style GdkCapStyle RW Cap ("endpoint") style for the line. + * join_style GdkJoinStyle RW Join ("vertex") style for the line. + * line_style GdkLineStyle RW Line dash style + * first_arrowhead boolean RW Specifies whether to draw an arrowhead on the + * first point of the line. + * last_arrowhead boolean RW Specifies whether to draw an arrowhead on the + * last point of the line. + * smooth boolean RW Specifies whether to smooth the line using + * parabolic splines. + * spline_steps uint RW Specifies the number of steps to use when rendering curves. + * arrow_shape_a double RW First arrow shape specifier. + * arrow_shape_b double RW Second arrow shape specifier. + * arrow_shape_c double RW Third arrow shape specifier. + */ + + +#define GNOME_TYPE_CANVAS_LINE (gnome_canvas_line_get_type ()) +#define GNOME_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_LINE, GnomeCanvasLine)) +#define GNOME_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_LINE, GnomeCanvasLineClass)) +#define GNOME_IS_CANVAS_LINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_LINE)) +#define GNOME_IS_CANVAS_LINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_LINE)) +#define GNOME_CANVAS_LINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_LINE, GnomeCanvasLineClass)) + + +typedef struct _GnomeCanvasLine GnomeCanvasLine; +typedef struct _GnomeCanvasLineClass GnomeCanvasLineClass; + +struct _GnomeCanvasLine { + GnomeCanvasItem item; + + double *coords; /* Array of coordinates for the line's points. X coords are in the + * even indices, Y coords are in the odd indices. If the line has + * arrowheads then the first and last points have been adjusted to + * refer to the necks of the arrowheads rather than their tips. The + * actual endpoints are stored in the first_arrow and last_arrow + * arrays, if they exist. + */ + + double *first_coords; /* Array of points describing polygon for the first arrowhead */ + double *last_coords; /* Array of points describing polygon for the last arrowhead */ + + GdkGC *gc; /* GC for drawing line */ + + GdkBitmap *stipple; /* Stipple pattern */ + + ArtSVP *fill_svp; /* The SVP for the outline shape */ /*AA*/ + ArtSVP *first_svp; /* The SVP for the first arrow */ /*AA*/ + ArtSVP *last_svp; /* The SVP for the last arrow */ /*AA*/ + + double width; /* Width of the line */ + + double shape_a; /* Distance from tip of arrowhead to center */ + double shape_b; /* Distance from tip of arrowhead to trailing point, measured along shaft */ + double shape_c; /* Distance of trailing points from outside edge of shaft */ + + GdkCapStyle cap; /* Cap style for line */ + GdkJoinStyle join; /* Join style for line */ + GdkLineStyle line_style;/* Style for the line */ + + gulong fill_pixel; /* Color for line */ + + guint32 fill_rgba; /* RGBA color for outline */ /*AA*/ + + int num_points; /* Number of points in the line */ + guint fill_color; /* Fill color, RGBA */ + + int spline_steps; /* Number of steps in each spline segment */ + + guint width_pixels : 1; /* Is the width specified in pixels or units? */ + guint first_arrow : 1; /* Draw first arrowhead? */ + guint last_arrow : 1; /* Draw last arrowhead? */ + guint smooth : 1; /* Smooth line (with parabolic splines)? */ +}; + +struct _GnomeCanvasLineClass { + GnomeCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_line_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-marshal.list b/libgnomecanvas/gnome-canvas-marshal.list new file mode 100644 index 0000000000..5ad61bf721 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-marshal.list @@ -0,0 +1,2 @@ +VOID:OBJECT,INT,INT,INT,INT +BOOLEAN:BOXED diff --git a/libgnomecanvas/gnome-canvas-path-def.c b/libgnomecanvas/gnome-canvas-path-def.c new file mode 100644 index 0000000000..11b0924689 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-path-def.c @@ -0,0 +1,1287 @@ +#define GNOME_CANVAS_PATH_DEF_C + +/* + * GnomeCanvasPathDef + * + * (C) 1999-2000 Lauris Kaplinski <lauris@ximian.com> + * Released under LGPL + * + * Authors: Lauris Kaplinski <lauris@ximian.com> + * Rusty Conover <rconover@bangtail.net> + * + * Copyright 1999-2001 Ximian Inc. and authors. + */ + +#include <string.h> +#include <libart_lgpl/art_misc.h> +#include "gnome-canvas-path-def.h" + +/* The number of points to allocate at once */ +#define GNOME_CANVAS_PATH_DEF_LENSTEP 32 + +struct _GnomeCanvasPathDef { + gint refcount; + ArtBpath * bpath; + gint end; /* ART_END position */ + gint length; /* Num allocated Bpaths */ + gint substart; /* subpath start */ + gdouble x, y; /* previous moveto position */ + guint sbpath : 1; /* Bpath is static */ + guint hascpt : 1; /* Currentpoint is defined */ + guint posset : 1; /* Previous was moveto */ + guint moving : 1; /* Bpath end is moving */ + guint allclosed : 1; /* All subpaths are closed */ + guint allopen : 1; /* All subpaths are open */ +}; + +static gboolean sp_bpath_good (ArtBpath * bpath); +static ArtBpath * sp_bpath_check_subpath (ArtBpath * bpath); +static gint sp_bpath_length (const ArtBpath * bpath); +static gboolean sp_bpath_all_closed (const ArtBpath * bpath); +static gboolean sp_bpath_all_open (const ArtBpath * bpath); + +/* Boxed Type Support */ + +static GnomeCanvasPathDef * +path_def_boxed_copy (GnomeCanvasPathDef * path_def) +{ + if (path_def) + gnome_canvas_path_def_ref (path_def); + return path_def; +} + +GType +gnome_canvas_path_def_get_type (void) +{ + static GType t = 0; + if (t == 0) + t = g_boxed_type_register_static + ("GnomeCanvasPathDef", + (GBoxedCopyFunc)path_def_boxed_copy, + (GBoxedFreeFunc)gnome_canvas_path_def_unref); + return t; +} + +/* Constructors */ + +/** + * gnome_canvas_path_def_new: + * + * This function creates a new empty #gnome_canvas_path_def. + * + * Returns: the new canvas path definition. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_new (void) +{ + GnomeCanvasPathDef * path; + + path = gnome_canvas_path_def_new_sized (GNOME_CANVAS_PATH_DEF_LENSTEP); + + return path; +} + +/** + * gnome_canvas_path_def_new_sized: + * @length: number of points to allocate for the path + * + * This function creates a new #gnome_canvas_path_def with @length + * number of points allocated. It is useful, if you know the exact + * number of points in path, so you can avoid automatic point + * array reallocation. + * + * Returns: the new canvas path definition + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_new_sized (gint length) +{ + GnomeCanvasPathDef * path; + + g_return_val_if_fail (length > 0, NULL); + + path = g_new (GnomeCanvasPathDef, 1); + + path->refcount = 1; + path->bpath = art_new (ArtBpath, length); + path->end = 0; + path->bpath[path->end].code = ART_END; + path->length = length; + path->sbpath = FALSE; + path->hascpt = FALSE; + path->posset = FALSE; + path->moving = FALSE; + path->allclosed = TRUE; + path->allopen = TRUE; + + return path; +} + +/** + * gnome_canvas_path_def_new_from_bpath: + * @bpath: libart bezier path + * + * This function constructs a new #gnome_canvas_path_def and uses the + * passed @bpath as the contents. The passed bpath should not be + * static as the path definition is editable when constructed with + * this function. Also, passed bpath will be freed with art_free, if + * path is destroyed, so use it with caution. + * For constructing a #gnome_canvas_path_def + * from (non-modifiable) bpath use + * #gnome_canvas_path_def_new_from_static_bpath. + * + * Returns: the new canvas path definition that is populated with the + * passed bezier path, if the @bpath is bad NULL is returned. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_new_from_bpath (ArtBpath * bpath) +{ + GnomeCanvasPathDef * path; + + g_return_val_if_fail (sp_bpath_good (bpath), NULL); + + path = g_new (GnomeCanvasPathDef, 1); + + path->refcount = 1; + path->bpath = bpath; + path->length = sp_bpath_length (bpath); + path->end = path->length - 1; + path->sbpath = FALSE; + path->hascpt = FALSE; + path->posset = FALSE; + path->moving = FALSE; + path->allclosed = sp_bpath_all_closed (bpath); + path->allopen = sp_bpath_all_open (bpath); + + return path; +} + +/** + * gnome_canvas_path_def_new_from_static_bpath: + * @bpath: libart bezier path + * + * This function constructs a new #gnome_canvas_path_def and + * references the passed @bpath as its contents. The + * #gnome_canvas_path_def returned from this function is to be + * considered static and non-editable (meaning you cannot change the + * path from what you passed in @bpath). The bpath will not be freed, + * if path will be destroyed, so use it with caution. + * + * Returns: the new canvas path definition that references the passed + * @bpath, or if the path is bad NULL is returned. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_new_from_static_bpath (ArtBpath * bpath) +{ + GnomeCanvasPathDef * path; + + g_return_val_if_fail (sp_bpath_good (bpath), NULL); + + path = g_new (GnomeCanvasPathDef, 1); + + path->refcount = 1; + path->bpath = bpath; + path->length = sp_bpath_length (bpath); + path->end = path->length - 1; + path->sbpath = TRUE; + path->hascpt = FALSE; + path->posset = FALSE; + path->moving = FALSE; + path->allclosed = sp_bpath_all_closed (bpath); + path->allopen = sp_bpath_all_open (bpath); + + return path; +} + +/** + * gnome_canvas_path_def_new_from_foreign_bpath: + * @bpath: libart bezier path + * + * This function constructs a new #gnome_canvas_path_def and + * duplicates the contents of the passed @bpath in the definition. + * + * Returns: the newly created #gnome_canvas_path_def or NULL if the + * path is invalid. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_new_from_foreign_bpath (ArtBpath * bpath) +{ + GnomeCanvasPathDef * path; + gint length; + + g_return_val_if_fail (sp_bpath_good (bpath), NULL); + + length = sp_bpath_length (bpath); + + path = gnome_canvas_path_def_new_sized (length); + memcpy (path->bpath, bpath, sizeof (ArtBpath) * length); + path->end = length - 1; + path->allclosed = sp_bpath_all_closed (bpath); + path->allopen = sp_bpath_all_open (bpath); + + return path; +} + +/** + * gnome_canvas_path_def_ref: + * @path: a GnomeCanvasPathDef + * + * Increment the reference count of the GnomeCanvasPathDef. + */ +void +gnome_canvas_path_def_ref (GnomeCanvasPathDef * path) +{ + g_return_if_fail (path != NULL); + + path->refcount++; +} + +/** + * gnome_canvas_path_def_finish: + * @path: a GnomeCanvasPathDef + * + * Trims dynamic point array to exact length of path. + */ +void +gnome_canvas_path_def_finish (GnomeCanvasPathDef * path) +{ + g_return_if_fail (path != NULL); + g_return_if_fail (path->sbpath); + + if ((path->end + 1) < path->length) { + path->bpath = art_renew (path->bpath, ArtBpath, path->end + 1); + path->length = path->end + 1; + } + + path->hascpt = FALSE; + path->posset = FALSE; + path->moving = FALSE; +} +/** + * gnome_canvas_path_def_ensure_space: + * @path: a GnomeCanvasPathDef + * @space: number of points to guarantee are allocated at the end of + * the path. + * + * This function ensures that enough space for @space points is + * allocated at the end of the path. + */ +void +gnome_canvas_path_def_ensure_space (GnomeCanvasPathDef * path, gint space) +{ + g_return_if_fail (path != NULL); + g_return_if_fail (space > 0); + + if (path->end + space < path->length) return; + + if (space < GNOME_CANVAS_PATH_DEF_LENSTEP) space = GNOME_CANVAS_PATH_DEF_LENSTEP; + + path->bpath = art_renew (path->bpath, ArtBpath, path->length + space); + + path->length += space; +} + +/** + * gnome_canvas_path_def_copy: + * @dst: a GnomeCanvasPathDef where the contents of @src will be stored. + * @src: a GnomeCanvasPathDef whose contents will be copied it @src. + * + * This function copies the contents @src to @dest. The old @dest path + * array is freed and @dest is marked as non-static (editable), + * regardless of the status of @src. + */ +void +gnome_canvas_path_def_copy (GnomeCanvasPathDef * dst, const GnomeCanvasPathDef * src) +{ + g_return_if_fail (dst != NULL); + g_return_if_fail (src != NULL); + + if (!dst->sbpath) g_free (dst->bpath); + + memcpy (dst, src, sizeof (GnomeCanvasPathDef)); + + dst->bpath = g_new (ArtBpath, src->end + 1); + memcpy (dst->bpath, src->bpath, (src->end + 1) * sizeof (ArtBpath)); + + dst->sbpath = FALSE; +} + + +/** + * gnome_canvas_path_def_duplicate: + * @path: a GnomeCanvasPathDef to duplicate + * + * This function duplicates the passed @path. The new path is + * marked as non-static regardless of the state of original. + * + * Returns: a GnomeCanvasPathDef which is a duplicate of @path. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_duplicate (const GnomeCanvasPathDef * path) +{ + GnomeCanvasPathDef * new; + + g_return_val_if_fail (path != NULL, NULL); + + new = gnome_canvas_path_def_new_from_foreign_bpath (path->bpath); + new->x = path->x; + new->y = path->y; + new->hascpt = path->hascpt; + new->posset = path->posset; + new->moving = path->moving; + new->allclosed = path->allclosed; + new->allopen = path->allopen; + + return new; +} + +/** + * gnome_canvas_path_def_concat: + * @list: a GSList of GnomeCanvasPathDefs to concatenate into one new + * path. + * + * This function concatenates a list of GnomeCanvasPathDefs into one + * newly created GnomeCanvasPathDef. + * + * Returns: a new GnomeCanvasPathDef + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_concat (const GSList * list) +{ + GnomeCanvasPathDef * c, * new; + ArtBpath * bp; + const GSList * l; + gint length; + + g_return_val_if_fail (list != NULL, NULL); + + length = 1; + + for (l = list; l != NULL; l = l->next) { + c = (GnomeCanvasPathDef *) l->data; + length += c->end; + } + + new = gnome_canvas_path_def_new_sized (length); + + bp = new->bpath; + + for (l = list; l != NULL; l = l->next) { + c = (GnomeCanvasPathDef *) l->data; + memcpy (bp, c->bpath, c->end * sizeof (ArtBpath)); + bp += c->end; + } + + bp->code = ART_END; + + new->end = length - 1; + + new->allclosed = sp_bpath_all_closed (new->bpath); + new->allopen = sp_bpath_all_open (new->bpath); + + return new; +} + +/** + * gnome_canvas_path_def_split: + * @path: a GnomeCanvasPathDef + * + * This function splits the passed @path into a list of + * GnomeCanvasPathDefs which represent each segment of the origional + * path. The path is split when ever an ART_MOVETO or ART_MOVETO_OPEN + * is encountered. The closedness of resulting paths is set accordingly + * to closedness of corresponding segment. + * + * Returns: a list of GnomeCanvasPathDef(s). + */ +GSList * +gnome_canvas_path_def_split (const GnomeCanvasPathDef * path) +{ + GnomeCanvasPathDef * new; + GSList * l; + gint p, i; + + g_return_val_if_fail (path != NULL, NULL); + + p = 0; + l = NULL; + + while (p < path->end) { + i = 1; + while ((path->bpath[p + i].code == ART_LINETO) || (path->bpath[p + i].code == ART_CURVETO)) i++; + new = gnome_canvas_path_def_new_sized (i + 1); + memcpy (new->bpath, path->bpath + p, i * sizeof (ArtBpath)); + new->end = i; + new->bpath[i].code = ART_END; + new->allclosed = (new->bpath->code == ART_MOVETO); + new->allopen = (new->bpath->code == ART_MOVETO_OPEN); + l = g_slist_append (l, new); + p += i; + } + + return l; +} + +/** + * gnome_canvas_path_def_open_parts: + * @path: a GnomeCanvasPathDef + * + * This function creates a new GnomeCanvasPathDef that contains all of + * the open segments on the passed @path. + * + * Returns: a new GnomeCanvasPathDef that contains all of the open segemtns in @path. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_open_parts (const GnomeCanvasPathDef * path) +{ + GnomeCanvasPathDef * new; + ArtBpath * p, * d; + gint len; + gboolean closed; + + g_return_val_if_fail (path != NULL, NULL); + + closed = TRUE; + len = 0; + + for (p = path->bpath; p->code != ART_END; p++) { + switch (p->code) { + case ART_MOVETO_OPEN: + closed = FALSE; + len++; + break; + case ART_MOVETO: + closed = TRUE; + break; + case ART_LINETO: + case ART_CURVETO: + if (!closed) len++; + break; + default: + g_assert_not_reached (); + } + } + + new = gnome_canvas_path_def_new_sized (len + 1); + + closed = TRUE; + d = new->bpath; + + for (p = path->bpath; p->code != ART_END; p++) { + switch (p->code) { + case ART_MOVETO_OPEN: + closed = FALSE; + *d++ = *p; + break; + case ART_MOVETO: + closed = TRUE; + break; + case ART_LINETO: + case ART_CURVETO: + if (!closed) *d++ = *p; + break; + default: + g_assert_not_reached (); + } + } + + d->code = ART_END; + + new->end = len; + new->allclosed = FALSE; + new->allopen = TRUE; + + return new; +} + +/** + * gnome_canvas_path_def_closed_parts: + * @path: a GnomeCanvasPathDef + * + * This function returns a new GnomeCanvasPathDef that contains the + * all of close parts of passed @path. + * + * Returns: a new GnomeCanvasPathDef that contains all of the closed + * parts of passed @path. + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_closed_parts (const GnomeCanvasPathDef * path) +{ + GnomeCanvasPathDef * new; + ArtBpath * p, * d; + gint len; + gboolean closed; + + g_return_val_if_fail (path != NULL, NULL); + + closed = FALSE; + len = 0; + + for (p = path->bpath; p->code != ART_END; p++) { + switch (p->code) { + case ART_MOVETO_OPEN: + closed = FALSE; + break; + case ART_MOVETO: + closed = TRUE; + len++; + break; + case ART_LINETO: + case ART_CURVETO: + if (closed) len++; + break; + default: + g_assert_not_reached (); + } + } + + new = gnome_canvas_path_def_new_sized (len + 1); + + closed = FALSE; + d = new->bpath; + + for (p = path->bpath; p->code != ART_END; p++) { + switch (p->code) { + case ART_MOVETO_OPEN: + closed = FALSE; + break; + case ART_MOVETO: + closed = TRUE; + *d++ = *p; + break; + case ART_LINETO: + case ART_CURVETO: + if (closed) *d++ = *p; + break; + default: + g_assert_not_reached (); + } + } + + d->code = ART_END; + + new->end = len; + new->allclosed = TRUE; + new->allopen = FALSE; + + return new; +} + +/** + * gnome_canvas_path_def_close_all: + * @path: a GnomeCanvasPathDef + * + * This function closes all of the open segments in the passed path + * and returns a new GnomeCanvasPathDef. + * + * Returns: a GnomeCanvasPathDef that contains the contents of @path + * but has modified the path is fully closed + */ +GnomeCanvasPathDef * +gnome_canvas_path_def_close_all (const GnomeCanvasPathDef * path) +{ + GnomeCanvasPathDef * new; + ArtBpath * p, * d, * start; + gint len; + gboolean closed; + + g_return_val_if_fail (path != NULL, NULL); + + if (path->allclosed) { + new = gnome_canvas_path_def_duplicate (path); + return new; + } + + len = 1; + + /* Count MOVETO_OPEN */ + + for (p = path->bpath; p->code != ART_END; p++) { + len += 1; + if (p->code == ART_MOVETO_OPEN) len += 2; + } + + new = gnome_canvas_path_def_new_sized (len); + + d = start = new->bpath; + closed = TRUE; + + for (p = path->bpath; p->code != ART_END; p++) { + switch (p->code) { + case ART_MOVETO_OPEN: + start = p; + closed = FALSE; + case ART_MOVETO: + if ((!closed) && ((start->x3 != p->x3) || (start->y3 != p->y3))) { + d->code = ART_LINETO; + d->x3 = start->x3; + d->y3 = start->y3; + d++; + } + if (p->code == ART_MOVETO) closed = TRUE; + d->code = ART_MOVETO; + d->x3 = p->x3; + d->y3 = p->y3; + d++; + break; + case ART_LINETO: + case ART_CURVETO: + *d++ = *p; + break; + default: + g_assert_not_reached (); + } + } + + if ((!closed) && ((start->x3 != p->x3) || (start->y3 != p->y3))) { + d->code = ART_LINETO; + d->x3 = start->x3; + d->y3 = start->y3; + d++; + } + + d->code = ART_END; + + new->end = d - new->bpath; + new->allclosed = TRUE; + new->allopen = FALSE; + + return new; +} + +/* Destructor */ + +/** + * gnome_canvas_path_def_unref: + * @path: a GnomeCanvasPathDef + * + * Decrease the reference count of the passed @path. If the reference + * count is < 1 the path is deallocated. + */ +void +gnome_canvas_path_def_unref (GnomeCanvasPathDef * path) +{ + g_return_if_fail (path != NULL); + + if (--path->refcount < 1) { + if ((!path->sbpath) && (path->bpath)) art_free (path->bpath); + g_free (path); + } +} + + +/* Methods */ +/** + * gnome_canvas_path_def_reset: + * @path: a GnomeCanvasPathDef + * + * This function clears the contents of the passed @path. + */ +void +gnome_canvas_path_def_reset (GnomeCanvasPathDef * path) +{ + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + + path->bpath->code = ART_END; + path->end = 0; + path->hascpt = FALSE; + path->posset = FALSE; + path->moving = FALSE; + path->allclosed = TRUE; + path->allopen = TRUE; +} + +/* Several consequtive movetos are ALLOWED */ + +/** + * gnome_canvas_path_def_moveto: + * @path: a GnomeCanvasPathDef + * @x: x coordinate + * @y: y coordinate + * + * This function adds starts new subpath on @path, and sets its + * starting point to @x and @y. If current subpath is empty, it + * simply changes its starting coordinates to new values. + */ +void +gnome_canvas_path_def_moveto (GnomeCanvasPathDef * path, gdouble x, gdouble y) +{ + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (!path->moving); + + path->substart = path->end; + path->hascpt = TRUE; + path->posset = TRUE; + path->x = x; + path->y = y; + + path->allclosed = FALSE; +} + +/** + * gnome_canvas_path_def_lineto: + * @path: a GnomeCanvasPathDef + * @x: x coordinate + * @y: y coordinate + * + * This function add a line segment to the passed @path with the + * specified @x and @y coordinates. + */ +void +gnome_canvas_path_def_lineto (GnomeCanvasPathDef * path, gdouble x, gdouble y) +{ + ArtBpath * bp; + + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (path->hascpt); + + if (path->moving) { + /* simply fix endpoint */ + g_return_if_fail (!path->posset); + g_return_if_fail (path->end > 1); + bp = path->bpath + path->end - 1; + g_return_if_fail (bp->code == ART_LINETO); + bp->x3 = x; + bp->y3 = y; + path->moving = FALSE; + return; + } + + if (path->posset) { + /* start a new segment */ + gnome_canvas_path_def_ensure_space (path, 2); + bp = path->bpath + path->end; + bp->code = ART_MOVETO_OPEN; + bp->x3 = path->x; + bp->y3 = path->y; + bp++; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = ART_END; + path->end += 2; + path->posset = FALSE; + path->allclosed = FALSE; + return; + } + + /* Simply add line */ + + g_return_if_fail (path->end > 1); + gnome_canvas_path_def_ensure_space (path, 1); + bp = path->bpath + path->end; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = ART_END; + path->end++; +} + + +/** + * gnome_canvas_path_def_lineto_moving: + * @path: a GnomeCanvasPathDef + * @x: x coordinate + * @y: y coordinate + * + * This functions adds a new line segment with loose endpoint to the path, or + * if endpoint is already loose, changes its coordinates to @x, @y. You + * can change the coordinates of loose endpoint as many times as you want, + * the last ones set will be fixed, if you continue line. This is useful + * for handling drawing with mouse. + */ +void +gnome_canvas_path_def_lineto_moving (GnomeCanvasPathDef * path, gdouble x, gdouble y) +{ + ArtBpath * bp; + + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (path->hascpt); + + if (path->moving) { + /* simply change endpoint */ + g_return_if_fail (!path->posset); + g_return_if_fail (path->end > 1); + bp = path->bpath + path->end - 1; + g_return_if_fail (bp->code == ART_LINETO); + bp->x3 = x; + bp->y3 = y; + return; + } + + if (path->posset) { + /* start a new segment */ + gnome_canvas_path_def_ensure_space (path, 2); + bp = path->bpath + path->end; + bp->code = ART_MOVETO_OPEN; + bp->x3 = path->x; + bp->y3 = path->y; + bp++; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = ART_END; + path->end += 2; + path->posset = FALSE; + path->moving = TRUE; + path->allclosed = FALSE; + return; + } + + /* Simply add line */ + + g_return_if_fail (path->end > 1); + gnome_canvas_path_def_ensure_space (path, 1); + bp = path->bpath + path->end; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; + bp++; + bp->code = ART_END; + path->end++; + path->moving = TRUE; +} + +/** + * gnome_canvas_path_def_curveto: + * @path: a GnomeCanvasPathDef + * @x0: first control point x coordinate + * @y0: first control point y coordinate + * @x1: second control point x coordinate + * @y1: second control point y coordinate + * @x2: end of curve x coordinate + * @y2: end of curve y coordinate + * + * This function adds a bezier curve segment to the path definition. + */ + +void +gnome_canvas_path_def_curveto (GnomeCanvasPathDef * path, gdouble x0, gdouble y0, gdouble x1, gdouble y1, gdouble x2, gdouble y2) +{ + ArtBpath * bp; + + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (path->hascpt); + g_return_if_fail (!path->moving); + + if (path->posset) { + /* start a new segment */ + gnome_canvas_path_def_ensure_space (path, 2); + bp = path->bpath + path->end; + bp->code = ART_MOVETO_OPEN; + bp->x3 = path->x; + bp->y3 = path->y; + bp++; + bp->code = ART_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = ART_END; + path->end += 2; + path->posset = FALSE; + path->allclosed = FALSE; + return; + } + + /* Simply add path */ + + g_return_if_fail (path->end > 1); + gnome_canvas_path_def_ensure_space (path, 1); + bp = path->bpath + path->end; + bp->code = ART_CURVETO; + bp->x1 = x0; + bp->y1 = y0; + bp->x2 = x1; + bp->y2 = y1; + bp->x3 = x2; + bp->y3 = y2; + bp++; + bp->code = ART_END; + path->end++; +} + +/** + * gnome_canvas_path_def_closepath: + * @path: a GnomeCanvasPathDef + * + * This function closes the last subpath of @path, adding a ART_LINETO to + * subpath starting point, if needed and changing starting pathcode to + * ART_MOVETO + */ +void +gnome_canvas_path_def_closepath (GnomeCanvasPathDef * path) +{ + ArtBpath * bs, * be; + + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (path->hascpt); + g_return_if_fail (!path->posset); + g_return_if_fail (!path->moving); + g_return_if_fail (!path->allclosed); + /* We need at last M + L + L + E */ + g_return_if_fail (path->end - path->substart > 2); + + bs = path->bpath + path->substart; + be = path->bpath + path->end - 1; + + if ((bs->x3 != be->x3) || (bs->y3 != be->y3)) { + gnome_canvas_path_def_lineto (path, bs->x3, bs->y3); + } + + bs = path->bpath + path->substart; /* NB. def_lineto can + realloc bpath */ + bs->code = ART_MOVETO; + + path->allclosed = sp_bpath_all_closed (path->bpath); + path->allopen = sp_bpath_all_open (path->bpath); + + path->hascpt = FALSE; +} + +/** + * gnome_canvas_path_def_closepath_current: + * @path: a GnomeCanvasPathDef + * + * This function closes the last subpath by setting the coordinates of + * the endpoint of the last segment (line or curve) to starting point. + */ +void +gnome_canvas_path_def_closepath_current (GnomeCanvasPathDef * path) +{ + ArtBpath * bs, * be; + + g_return_if_fail (path != NULL); + g_return_if_fail (!path->sbpath); + g_return_if_fail (path->hascpt); + g_return_if_fail (!path->posset); + g_return_if_fail (!path->allclosed); + /* We need at last M + L + L + E */ + g_return_if_fail (path->end - path->substart > 2); + + bs = path->bpath + path->substart; + be = path->bpath + path->end - 1; + + be->x3 = bs->x3; + be->y3 = bs->y3; + + bs->code = ART_MOVETO; + + path->allclosed = sp_bpath_all_closed (path->bpath); + path->allopen = sp_bpath_all_open (path->bpath); + + path->hascpt = FALSE; + path->moving = FALSE; +} + +/** + * gnome_canvas_path_def_bpath: + * @path: a GnomeCanvasPathDef + * + * This function returns a ArtBpath that consists of the path + * definition. + * + * Returns: ArtBpath + */ +ArtBpath * gnome_canvas_path_def_bpath (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return path->bpath; +} + +/** + * gnome_canvas_path_def_length: + * @path: a GnomeCanvasPathDef + * + * This function returns the length of the path definition. Not + * Euclidian length of the path but rather the number of points on the + * path. + * + * Returns: integer, number of points on the path. + */ +gint gnome_canvas_path_def_length (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, -1); + + return path->end + 1; +} + +/** + * gnome_canvas_path_def_is_empty: + * @path: a GnomeCanvasPathDef + * + * This function is a boolean test to see if the path is empty, + * meaning containing no line segments. + * + * Returns: boolean, indicating if the path is empty. + */ +gboolean +gnome_canvas_path_def_is_empty (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, TRUE); + + return (path->bpath->code == ART_END); +} + +/** + * gnome_canvas_path_def_has_currentpoint: + * @path: a GnomeCanvasPathdef + * + * This function is a boolean test checking to see if the path has a + * current point defined. Current point will be set by line operators, + * and cleared by closing subpath. + * + * Returns: boolean, indicating if the path has a current point defined. + */ +gboolean +gnome_canvas_path_def_has_currentpoint (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, FALSE); + + return (path->hascpt); +} + +/** + * gnome_canvas_path_def_currentpoint: + * @path: a GnomeCanvasPathDef + * @p: a ArtPoint where to store the current point + * + * Stores the current point of the path definition in the passed ArtPoint @p. + */ +void +gnome_canvas_path_def_currentpoint (const GnomeCanvasPathDef * path, ArtPoint * p) +{ + g_return_if_fail (path != NULL); + g_return_if_fail (p != NULL); + g_return_if_fail (path->hascpt); + + if (path->posset) { + p->x = path->x; + p->y = path->y; + } else { + p->x = (path->bpath + path->end - 1)->x3; + p->y = (path->bpath + path->end - 1)->y3; + } +} + +/** + * gnome_canvas_path_def_last_bpath: + * @path: a GnomeCanvasPathDef + * + * This function returns pointer to the last ArtBpath segment in the path + * definition. + * + * Returns: ArtBpath, being the last segment in the path definition or + * null if no line segments have been defined. + */ +ArtBpath * +gnome_canvas_path_def_last_bpath (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, NULL); + + if (path->end == 0) return NULL; + + return path->bpath + path->end - 1; +} + +/** + * gnome_canvas_path_def_first_bpath: + * @path: a GnomeCanvasPathDef + * + * This function returns the first ArtBpath point in the definition. + * + * Returns: ArtBpath being the first point in the path definition or + * null if no points are defined +*/ +ArtBpath * +gnome_canvas_path_def_first_bpath (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, NULL); + + if (path->end == 0) return NULL; + + return path->bpath; +} + +/** + * gnome_canvas_path_def_any_open: + * @path: a GnomeCanvasPathDef + * + * This function returns a boolean value indicating if the path has + * any open segments. + * + * Returns: boolean, indicating if the path has any open segments. + */ +gboolean +gnome_canvas_path_def_any_open (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, FALSE); + + return (!path->allclosed); +} + +/** + * gnome_canvas_path_def_all_open: + * @path: a GnomeCanvasPathDef + * + * This function returns a boolean value indicating if the path only + * contains open segments. + * + * Returns: boolean, indicating if the path has all open segments. + */ +gboolean +gnome_canvas_path_def_all_open (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, FALSE); + + return (path->allopen); +} + +/** + * gnome_canvas_path_def_any_closed: + * @path: a GnomeCanvasPathDef + * + * This function returns a boolean valid indicating if the path has + * any closed segements. + * + * Returns: boolean, indicating if the path has any closed segments. + */ +gboolean +gnome_canvas_path_def_any_closed (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, FALSE); + + return (!path->allopen); +} + +/** + * gnome_canvas_path_def_all_closed: + * @path: a GnomeCanvasPathDef + * + * This function returns a boolean value indicating if the path only + * contains closed segments. + * + * Returns: boolean, indicating if the path has all closed segments. + */ +gboolean +gnome_canvas_path_def_all_closed (const GnomeCanvasPathDef * path) +{ + g_return_val_if_fail (path != NULL, FALSE); + + return (path->allclosed); +} + +/* Private methods */ + +static +gboolean sp_bpath_good (ArtBpath * bpath) +{ + ArtBpath * bp; + + g_return_val_if_fail (bpath != NULL, FALSE); + + if (bpath->code == ART_END) + return TRUE; + + bp = bpath; + + while (bp->code != ART_END) { + bp = sp_bpath_check_subpath (bp); + if (bp == NULL) return FALSE; + } + + return TRUE; +} + +static ArtBpath * +sp_bpath_check_subpath (ArtBpath * bpath) +{ + gint i, len; + gboolean closed; + + g_return_val_if_fail (bpath != NULL, NULL); + + if (bpath->code == ART_MOVETO) { + closed = TRUE; + } else if (bpath->code == ART_MOVETO_OPEN) { + closed = FALSE; + } else { + return NULL; + } + + len = 0; + + for (i = 1; (bpath[i].code != ART_END) && (bpath[i].code != ART_MOVETO) && (bpath[i].code != ART_MOVETO_OPEN); i++) { + switch (bpath[i].code) { + case ART_LINETO: + case ART_CURVETO: + len++; + break; + default: + return NULL; + } + } + + if (closed) { + if (len < 2) return NULL; + if ((bpath->x3 != bpath[i-1].x3) || (bpath->y3 != bpath[i-1].y3)) return NULL; + } else { + if (len < 1) return NULL; + } + + return bpath + i; +} + +static gint +sp_bpath_length (const ArtBpath * bpath) +{ + gint l; + + g_return_val_if_fail (bpath != NULL, FALSE); + + for (l = 0; bpath[l].code != ART_END; l++) ; + + l++; + + return l; +} + +static gboolean +sp_bpath_all_closed (const ArtBpath * bpath) +{ + const ArtBpath * bp; + + g_return_val_if_fail (bpath != NULL, FALSE); + + for (bp = bpath; bp->code != ART_END; bp++) + if (bp->code == ART_MOVETO_OPEN) return FALSE; + + return TRUE; +} + +static gboolean +sp_bpath_all_open (const ArtBpath * bpath) +{ + const ArtBpath * bp; + + g_return_val_if_fail (bpath != NULL, FALSE); + + for (bp = bpath; bp->code != ART_END; bp++) + if (bp->code == ART_MOVETO) return FALSE; + + return TRUE; +} + + diff --git a/libgnomecanvas/gnome-canvas-path-def.h b/libgnomecanvas/gnome-canvas-path-def.h new file mode 100644 index 0000000000..c3f6b25147 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-path-def.h @@ -0,0 +1,96 @@ +#ifndef GNOME_CANVAS_PATH_DEF_H +#define GNOME_CANVAS_PATH_DEF_H + +/* + * GnomeCanvasPathDef + * + * (C) 1999-2000 Lauris Kaplinski <lauris@ximian.com> + * Released under LGPL + * + * This is mostly like GnomeCanvasBpathDef, but with added functionality: + * - can be constructed from scratch, from existing bpath of from static bpath + * - Path is always terminated with ART_END + * - Has closed flag + * - has concat, split and copy methods + * + */ + +#include <glib-object.h> +#include <libart_lgpl/art_bpath.h> + +G_BEGIN_DECLS + +typedef struct _GnomeCanvasPathDef GnomeCanvasPathDef; + +#define GNOME_TYPE_CANVAS_PATH_DEF (gnome_canvas_path_def_get_type ()) +GType gnome_canvas_path_def_get_type (void) G_GNUC_CONST; + +/* Constructors */ + +GnomeCanvasPathDef * gnome_canvas_path_def_new (void); +GnomeCanvasPathDef * gnome_canvas_path_def_new_sized (gint length); +GnomeCanvasPathDef * gnome_canvas_path_def_new_from_bpath (ArtBpath * bpath); +GnomeCanvasPathDef * gnome_canvas_path_def_new_from_static_bpath (ArtBpath * bpath); +GnomeCanvasPathDef * gnome_canvas_path_def_new_from_foreign_bpath (ArtBpath * bpath); + +void gnome_canvas_path_def_ref (GnomeCanvasPathDef * path); +void gnome_canvas_path_def_finish (GnomeCanvasPathDef * path); +void gnome_canvas_path_def_ensure_space (GnomeCanvasPathDef * path, gint space); + +/* + * Misc constructors + * All these return NEW path, not unrefing old + * Also copy and duplicate force bpath to be private (otherwise you + * would use ref :) + */ + +void gnome_canvas_path_def_copy (GnomeCanvasPathDef * dst, const GnomeCanvasPathDef * src); +GnomeCanvasPathDef * gnome_canvas_path_def_duplicate (const GnomeCanvasPathDef * path); +GnomeCanvasPathDef * gnome_canvas_path_def_concat (const GSList * list); +GSList * gnome_canvas_path_def_split (const GnomeCanvasPathDef * path); +GnomeCanvasPathDef * gnome_canvas_path_def_open_parts (const GnomeCanvasPathDef * path); +GnomeCanvasPathDef * gnome_canvas_path_def_closed_parts (const GnomeCanvasPathDef * path); +GnomeCanvasPathDef * gnome_canvas_path_def_close_all (const GnomeCanvasPathDef * path); + +/* Destructor */ + +void gnome_canvas_path_def_unref (GnomeCanvasPathDef * path); + +/* Methods */ + +/* Sets GnomeCanvasPathDef to zero length */ + +void gnome_canvas_path_def_reset (GnomeCanvasPathDef * path); + +/* Drawing methods */ + +void gnome_canvas_path_def_moveto (GnomeCanvasPathDef * path, gdouble x, gdouble y); +void gnome_canvas_path_def_lineto (GnomeCanvasPathDef * path, gdouble x, gdouble y); + +/* Does not create new ArtBpath, but simply changes last lineto position */ + +void gnome_canvas_path_def_lineto_moving (GnomeCanvasPathDef * path, gdouble x, gdouble y); +void gnome_canvas_path_def_curveto (GnomeCanvasPathDef * path, gdouble x0, gdouble y0,gdouble x1, gdouble y1, gdouble x2, gdouble y2); +void gnome_canvas_path_def_closepath (GnomeCanvasPathDef * path); + +/* Does not draw new line to startpoint, but moves last lineto */ + +void gnome_canvas_path_def_closepath_current (GnomeCanvasPathDef * path); + +/* Various methods */ + +ArtBpath * gnome_canvas_path_def_bpath (const GnomeCanvasPathDef * path); +gint gnome_canvas_path_def_length (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_is_empty (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_has_currentpoint (const GnomeCanvasPathDef * path); +void gnome_canvas_path_def_currentpoint (const GnomeCanvasPathDef * path, ArtPoint * p); +ArtBpath * gnome_canvas_path_def_last_bpath (const GnomeCanvasPathDef * path); +ArtBpath * gnome_canvas_path_def_first_bpath (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_any_open (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_all_open (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_any_closed (const GnomeCanvasPathDef * path); +gboolean gnome_canvas_path_def_all_closed (const GnomeCanvasPathDef * path); + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-pixbuf.c b/libgnomecanvas/gnome-canvas-pixbuf.c new file mode 100644 index 0000000000..9619e86df8 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-pixbuf.c @@ -0,0 +1,1077 @@ +/* GNOME libraries - GdkPixbuf item for the GNOME canvas + * + * Copyright (C) 1999 The Free Software Foundation + * + * Author: Federico Mena-Quintero <federico@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <config.h> +#include <math.h> +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-util.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <libart_lgpl/art_rgb_affine.h> +#include <libart_lgpl/art_rgb_rgba_affine.h> +#include "gnome-canvas-pixbuf.h" + +/* Private part of the GnomeCanvasPixbuf structure */ +typedef struct { + /* Our gdk-pixbuf */ + GdkPixbuf *pixbuf; + + /* Width value */ + double width; + + /* Height value */ + double height; + + /* X translation */ + double x; + + /* Y translation */ + double y; + + /* Whether dimensions are set and whether they are in pixels or units */ + guint width_set : 1; + guint width_in_pixels : 1; + guint height_set : 1; + guint height_in_pixels : 1; + guint x_in_pixels : 1; + guint y_in_pixels : 1; + + /* Whether the pixbuf has changed */ + guint need_pixbuf_update : 1; + + /* Whether the transformation or size have changed */ + guint need_xform_update : 1; + + /* Anchor */ + GtkAnchorType anchor; +} PixbufPrivate; + +/* Object argument IDs */ +enum { + PROP_0, + PROP_PIXBUF, + PROP_WIDTH, + PROP_WIDTH_SET, + PROP_WIDTH_IN_PIXELS, + PROP_HEIGHT, + PROP_HEIGHT_SET, + PROP_HEIGHT_IN_PIXELS, + PROP_X, + PROP_X_IN_PIXELS, + PROP_Y, + PROP_Y_IN_PIXELS, + PROP_ANCHOR +}; + +static void gnome_canvas_pixbuf_class_init (GnomeCanvasPixbufClass *class); +static void gnome_canvas_pixbuf_init (GnomeCanvasPixbuf *cpb); +static void gnome_canvas_pixbuf_destroy (GtkObject *object); +static void gnome_canvas_pixbuf_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_pixbuf_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_pixbuf_update (GnomeCanvasItem *item, double *affine, + ArtSVP *clip_path, int flags); +static void gnome_canvas_pixbuf_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); +static void gnome_canvas_pixbuf_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); +static double gnome_canvas_pixbuf_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item); +static void gnome_canvas_pixbuf_bounds (GnomeCanvasItem *item, + double *x1, double *y1, double *x2, double *y2); + +static GnomeCanvasItemClass *parent_class; + + + +/** + * gnome_canvas_pixbuf_get_type: + * @void: + * + * Registers the #GnomeCanvasPixbuf class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the #GnomeCanvasPixbuf class. + **/ +GType +gnome_canvas_pixbuf_get_type (void) +{ + static GType pixbuf_type; + + if (!pixbuf_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasPixbufClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_pixbuf_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasPixbuf), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_pixbuf_init, + NULL /* value_table */ + }; + + pixbuf_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasPixbuf", + &object_info, 0); + } + + return pixbuf_type; +} + +/* Class initialization function for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_class_init (GnomeCanvasPixbufClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_pixbuf_set_property; + gobject_class->get_property = gnome_canvas_pixbuf_get_property; + + g_object_class_install_property + (gobject_class, + PROP_PIXBUF, + g_param_spec_object ("pixbuf", NULL, NULL, + GDK_TYPE_PIXBUF, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH, + g_param_spec_double ("width", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH_SET, + g_param_spec_boolean ("width_set", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH_IN_PIXELS, + g_param_spec_boolean ("width_in_pixels", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_HEIGHT, + g_param_spec_double ("height", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_HEIGHT_SET, + g_param_spec_boolean ("height_set", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_HEIGHT_IN_PIXELS, + g_param_spec_boolean ("height_in_pixels", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_X, + g_param_spec_double ("x", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_X_IN_PIXELS, + g_param_spec_boolean ("x_in_pixels", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y_IN_PIXELS, + g_param_spec_boolean ("y_in_pixels", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_NW, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_pixbuf_destroy; + + item_class->update = gnome_canvas_pixbuf_update; + item_class->draw = gnome_canvas_pixbuf_draw; + item_class->render = gnome_canvas_pixbuf_render; + item_class->point = gnome_canvas_pixbuf_point; + item_class->bounds = gnome_canvas_pixbuf_bounds; +} + +/* Object initialization function for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_init (GnomeCanvasPixbuf *gcp) +{ + PixbufPrivate *priv; + + priv = g_new0 (PixbufPrivate, 1); + gcp->priv = priv; + + priv->width = 0.0; + priv->height = 0.0; + priv->x = 0.0; + priv->y = 0.0; + priv->anchor = GTK_ANCHOR_NW; +} + +/* Destroy handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_destroy (GtkObject *object) +{ + GnomeCanvasItem *item; + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_PIXBUF (object)); + + item = GNOME_CANVAS_ITEM (object); + gcp = (GNOME_CANVAS_PIXBUF (object)); + priv = gcp->priv; + + /* remember, destroy can be run multiple times! */ + + if (priv) { + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + + if (priv->pixbuf) + g_object_unref (priv->pixbuf); + + g_free (priv); + gcp->priv = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + + + +/* Set_property handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + GdkPixbuf *pixbuf; + double val; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_PIXBUF (object)); + + item = GNOME_CANVAS_ITEM (object); + gcp = GNOME_CANVAS_PIXBUF (object); + priv = gcp->priv; + + switch (param_id) { + case PROP_PIXBUF: + if (g_value_get_object (value)) + pixbuf = GDK_PIXBUF (g_value_get_object (value)); + else + pixbuf = NULL; + if (pixbuf != priv->pixbuf) { + if (pixbuf) { + g_return_if_fail + (gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB); + g_return_if_fail + (gdk_pixbuf_get_n_channels (pixbuf) == 3 + || gdk_pixbuf_get_n_channels (pixbuf) == 4); + g_return_if_fail + (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8); + + g_object_ref (pixbuf); + } + + if (priv->pixbuf) + g_object_unref (priv->pixbuf); + + priv->pixbuf = pixbuf; + } + + priv->need_pixbuf_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_WIDTH: + val = g_value_get_double (value); + g_return_if_fail (val >= 0.0); + priv->width = val; + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_WIDTH_SET: + priv->width_set = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_WIDTH_IN_PIXELS: + priv->width_in_pixels = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_HEIGHT: + val = g_value_get_double (value); + g_return_if_fail (val >= 0.0); + priv->height = val; + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_HEIGHT_SET: + priv->height_set = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_HEIGHT_IN_PIXELS: + priv->height_in_pixels = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_X: + priv->x = g_value_get_double (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_X_IN_PIXELS: + priv->x_in_pixels = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_Y: + priv->y = g_value_get_double (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_Y_IN_PIXELS: + priv->y_in_pixels = g_value_get_boolean (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + case PROP_ANCHOR: + priv->anchor = g_value_get_enum (value); + priv->need_xform_update = TRUE; + gnome_canvas_item_request_update (item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +/* Get_property handler for the pixbuf canvasi item */ +static void +gnome_canvas_pixbuf_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_PIXBUF (object)); + + gcp = GNOME_CANVAS_PIXBUF (object); + priv = gcp->priv; + + switch (param_id) { + case PROP_PIXBUF: + g_value_set_object (value, G_OBJECT (priv->pixbuf)); + break; + + case PROP_WIDTH: + g_value_set_double (value, priv->width); + break; + + case PROP_WIDTH_SET: + g_value_set_boolean (value, priv->width_set); + break; + + case PROP_WIDTH_IN_PIXELS: + g_value_set_boolean (value, priv->width_in_pixels); + break; + + case PROP_HEIGHT: + g_value_set_double (value, priv->height); + break; + + case PROP_HEIGHT_SET: + g_value_set_boolean (value, priv->height_set); + break; + + case PROP_HEIGHT_IN_PIXELS: + g_value_set_boolean (value, priv->height_in_pixels); + break; + + case PROP_X: + g_value_set_double (value, priv->x); + break; + + case PROP_X_IN_PIXELS: + g_value_set_boolean (value, priv->x_in_pixels); + break; + + case PROP_Y: + g_value_set_double (value, priv->y); + break; + + case PROP_Y_IN_PIXELS: + g_value_set_boolean (value, priv->y_in_pixels); + break; + + case PROP_ANCHOR: + g_value_set_enum (value, priv->anchor); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + + +/* Bounds and utilities */ + +/* Computes the amount by which the unit horizontal and vertical vectors will be + * scaled by an affine transformation. + */ +static void +compute_xform_scaling (double *affine, ArtPoint *i_c, ArtPoint *j_c) +{ + ArtPoint orig, orig_c; + ArtPoint i, j; + + /* Origin */ + + orig.x = 0.0; + orig.y = 0.0; + art_affine_point (&orig_c, &orig, affine); + + /* Horizontal and vertical vectors */ + + i.x = 1.0; + i.y = 0.0; + art_affine_point (i_c, &i, affine); + i_c->x -= orig_c.x; + i_c->y -= orig_c.y; + + j.x = 0.0; + j.y = 1.0; + art_affine_point (j_c, &j, affine); + j_c->x -= orig_c.x; + j_c->y -= orig_c.y; +} + +/* computes the addtional resolution dependent affine needed to + * fit the image within its viewport defined by x,y,width and height + * args + */ +static void +compute_viewport_affine (GnomeCanvasPixbuf *gcp, double *viewport_affine, double *i2c) +{ + PixbufPrivate *priv; + ArtPoint i_c, j_c; + double i_len, j_len; + double si_len, sj_len; + double ti_len, tj_len; + double scale[6], translate[6]; + double w, h; + double x, y; + + priv = gcp->priv; + + /* Compute scaling vectors and required width/height */ + + compute_xform_scaling (i2c, &i_c, &j_c); + + i_len = sqrt (i_c.x * i_c.x + i_c.y * i_c.y); + j_len = sqrt (j_c.x * j_c.x + j_c.y * j_c.y); + + if (priv->width_set) + w = priv->width; + else + w = gdk_pixbuf_get_width (priv->pixbuf); + + if (priv->height_set) + h = priv->height; + else + h = gdk_pixbuf_get_height (priv->pixbuf); + + x = priv->x; + y = priv->y; + + /* Convert i_len and j_len into scaling factors */ + + if (priv->width_in_pixels) { + if (i_len > GNOME_CANVAS_EPSILON) + si_len = 1.0 / i_len; + else + si_len = 0.0; + } else + si_len = 1.0; + + si_len *= w / gdk_pixbuf_get_width (priv->pixbuf); + + if (priv->height_in_pixels) { + if (j_len > GNOME_CANVAS_EPSILON) + sj_len = 1.0 / j_len; + else + sj_len = 0.0; + } else + sj_len = 1.0; + + sj_len *= h / gdk_pixbuf_get_height (priv->pixbuf); + + /* Calculate translation offsets */ + + if (priv->x_in_pixels) { + if (i_len > GNOME_CANVAS_EPSILON) + ti_len = 1.0 / i_len; + else + ti_len = 0.0; + } else + ti_len = 1.0; + + switch (priv->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + ti_len *= x; + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + ti_len *= x - w * si_len / 2; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + ti_len *= x - w * si_len; + break; + + default: + break; + } + + if (priv->y_in_pixels) { + if (j_len > GNOME_CANVAS_EPSILON) + tj_len = 1.0 / j_len; + else + tj_len = 0.0; + } else + tj_len = 1.0; + + switch (priv->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + tj_len *= y; + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + tj_len *= y - h * sj_len / 2; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + tj_len *= y - h * sj_len; + break; + + default: + break; + } + + /* Compute the final affine */ + + art_affine_scale (scale, si_len, sj_len); + art_affine_translate (translate, ti_len, tj_len); + art_affine_multiply (viewport_affine, scale, translate); +} + +/* Computes the affine transformation with which the pixbuf needs to be + * transformed to render it on the canvas. This is not the same as the + * item_to_canvas transformation because we may need to scale the pixbuf + * by some other amount. + */ +static void +compute_render_affine (GnomeCanvasPixbuf *gcp, double *ra, double *i2c) +{ + double va[6]; + + compute_viewport_affine (gcp, va, i2c); +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("va %g %g %g %g %g %g\n", va[0], va[1], va[2], va[3], va[4], va[5]); +#endif + art_affine_multiply (ra, va, i2c); +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("ra %g %g %g %g %g %g\n", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]); +#endif +} + +/* Recomputes the bounding box of a pixbuf canvas item. The horizontal and + * vertical dimensions may be specified in units or pixels, separately, so we + * have to compute the components individually for each dimension. + */ +static void +recompute_bounding_box (GnomeCanvasPixbuf *gcp, gdouble *i2c) +{ + GnomeCanvasItem *item; + PixbufPrivate *priv; + double ra[6]; + ArtDRect rect; + + item = GNOME_CANVAS_ITEM (gcp); + priv = gcp->priv; + + if (!priv->pixbuf) { + item->x1 = item->y1 = item->x2 = item->y2 = 0.0; + return; + } + + rect.x0 = 0.0; + rect.x1 = gdk_pixbuf_get_width (priv->pixbuf); + + rect.y0 = 0.0; + rect.y1 = gdk_pixbuf_get_height (priv->pixbuf); + +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("i2c %g %g %g %g %g %g\n", i2c[0], i2c[1], i2c[2], i2c[3], i2c[4], i2c[5]); +#endif + gnome_canvas_item_i2c_affine (item, i2c); +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("i2c %g %g %g %g %g %g\n", i2c[0], i2c[1], i2c[2], i2c[3], i2c[4], i2c[5]); +#endif + compute_render_affine (gcp, ra, i2c); +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("ra %g %g %g %g %g %g\n", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]); +#endif + art_drect_affine_transform (&rect, &rect, ra); + + item->x1 = floor (rect.x0); + item->y1 = floor (rect.y0); + item->x2 = ceil (rect.x1); + item->y2 = ceil (rect.y1); +} + + + +/* Update sequence */ + +/* Update handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + + gcp = GNOME_CANVAS_PIXBUF (item); + priv = gcp->priv; + + if (parent_class->update) + (* parent_class->update) (item, affine, clip_path, flags); + + /* the optimzations below cause rarely triggered redrawing bugs and + * don't seem to actually save much performance. so it's probably + * better to turn them off, than to chase subtle optimization bugs + * throughgout all of gnome-canvas-pixbuf.c - TIMJ + */ +#ifdef USE_BROKEN_OPTIMIZATIONS + if (((flags & GNOME_CANVAS_UPDATE_VISIBILITY) + && !(GTK_OBJECT_FLAGS (item) & GNOME_CANVAS_ITEM_VISIBLE)) + || (flags & GNOME_CANVAS_UPDATE_AFFINE) + || priv->need_pixbuf_update + || priv->need_xform_update) { + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + } + + /* If we need a pixbuf update, or if the item changed visibility to + * shown, recompute the bounding box. + */ + if (priv->need_pixbuf_update + || priv->need_xform_update + || ((flags & GNOME_CANVAS_UPDATE_VISIBILITY) + && (GTK_OBJECT_FLAGS (gcp) & GNOME_CANVAS_ITEM_VISIBLE)) + || (flags & GNOME_CANVAS_UPDATE_AFFINE)) { + recompute_bounding_box (gcp, affine); +#ifdef GNOME_CANVAS_PIXBUF_VERBOSE + g_print ("BBox is %g %g %g %g\n", item->x1, item->y1, item->x2, item->y2); +#endif + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + priv->need_pixbuf_update = FALSE; + priv->need_xform_update = FALSE; + } +#else /* ordinary update logic */ + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + recompute_bounding_box (gcp, affine); + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + priv->need_pixbuf_update = FALSE; + priv->need_xform_update = FALSE; +#endif +} + + + +/* Rendering */ + +/* This is private to libart, but we need it. Sigh. */ +extern void art_rgb_affine_run (int *p_x0, int *p_x1, int y, int src_width, int src_height, + const double affine[6]); + +/* Fills the specified buffer with the transformed version of a pixbuf */ +static void +transform_pixbuf (guchar *dest, int x, int y, int width, int height, int rowstride, + GdkPixbuf *pixbuf, double *affine) +{ + int xx, yy; + double inv[6]; + guchar *src, *d; + ArtPoint src_p, dest_p; + int run_x1, run_x2; + int src_x, src_y; + int i; + + art_affine_invert (inv, affine); + + for (yy = 0; yy < height; yy++) { + dest_p.y = y + yy + 0.5; + + run_x1 = x; + run_x2 = x + width; + art_rgb_affine_run (&run_x1, &run_x2, yy + y, + gdk_pixbuf_get_width (pixbuf), + gdk_pixbuf_get_height (pixbuf), + inv); + + d = dest + yy * rowstride + (run_x1 - x) * 4; + + for (xx = run_x1; xx < run_x2; xx++) { + dest_p.x = xx + 0.5; + art_affine_point (&src_p, &dest_p, inv); + src_x = floor (src_p.x); + src_y = floor (src_p.y); + + src = + gdk_pixbuf_get_pixels (pixbuf) + src_y * + gdk_pixbuf_get_rowstride (pixbuf) + src_x * + gdk_pixbuf_get_n_channels (pixbuf); + + for (i = 0; i < gdk_pixbuf_get_n_channels (pixbuf); i++) + *d++ = *src++; + + if (!gdk_pixbuf_get_has_alpha(pixbuf)) + *d++ = 255; /* opaque */ + } + } +} + +/* Draw handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + double i2c[6], render_affine[6]; + guchar *buf; + GdkPixbuf *pixbuf; + ArtIRect p_rect, a_rect, d_rect; + int w, h; + + gcp = GNOME_CANVAS_PIXBUF (item); + priv = gcp->priv; + + if (!priv->pixbuf) + return; + + gnome_canvas_item_i2c_affine (item, i2c); + compute_render_affine (gcp, render_affine, i2c); + + /* Compute the area we need to repaint */ + + p_rect.x0 = item->x1; + p_rect.y0 = item->y1; + p_rect.x1 = item->x2; + p_rect.y1 = item->y2; + + a_rect.x0 = x; + a_rect.y0 = y; + a_rect.x1 = x + width; + a_rect.y1 = y + height; + + art_irect_intersect (&d_rect, &p_rect, &a_rect); + if (art_irect_empty (&d_rect)) + return; + + /* Create a temporary buffer and transform the pixbuf there */ + + w = d_rect.x1 - d_rect.x0; + h = d_rect.y1 - d_rect.y0; + + buf = g_new0 (guchar, w * h * 4); + transform_pixbuf (buf, + d_rect.x0, d_rect.y0, + w, h, + w * 4, + priv->pixbuf, render_affine); + + pixbuf = gdk_pixbuf_new_from_data (buf, GDK_COLORSPACE_RGB, + TRUE, + 8, w, h, + w * 4, + NULL, NULL); + + gdk_draw_pixbuf (drawable, NULL, pixbuf, + 0, 0, + d_rect.x0 - x, d_rect.y0 - y, + w, h, + GDK_RGB_DITHER_MAX, + d_rect.x0, d_rect.y0); + + g_object_unref (pixbuf); + g_free (buf); +} + +/* Render handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + double i2c[6], render_affine[6]; + + gcp = GNOME_CANVAS_PIXBUF (item); + priv = gcp->priv; + + if (!priv->pixbuf) + return; + + gnome_canvas_item_i2c_affine (item, i2c); + compute_render_affine (gcp, render_affine, i2c); + gnome_canvas_buf_ensure_buf (buf); + + + if ((fabs (render_affine[1]) < GNOME_CANVAS_EPSILON) && + (fabs (render_affine[2]) < GNOME_CANVAS_EPSILON) && + render_affine[0] > 0.0 && + render_affine[3] > 0.0) + { + GdkPixbuf *dest_pixbuf; + int x0, y0, x1, y1; + + dest_pixbuf = gdk_pixbuf_new_from_data (buf->buf, + GDK_COLORSPACE_RGB, + FALSE, + 8, + buf->rect.x1 - buf->rect.x0, + buf->rect.y1 - buf->rect.y0, + buf->buf_rowstride, + NULL, NULL); + + + x0 = floor (render_affine[4] - buf->rect.x0 + 0.5); + y0 = floor (render_affine[5] - buf->rect.y0 + 0.5); + + x1 = x0 + floor (gdk_pixbuf_get_width (priv->pixbuf) * render_affine[0] + 0.5); + y1 = y0 + floor (gdk_pixbuf_get_height (priv->pixbuf) * render_affine[3] + 0.5); + + x0 = MAX (x0, 0); + x0 = MIN (x0, buf->rect.x1 - buf->rect.x0); + y0 = MAX (y0, 0); + y0 = MIN (y0, buf->rect.y1 - buf->rect.y0); + + x1 = MAX (x1, 0); + x1 = MIN (x1, buf->rect.x1 - buf->rect.x0); + y1 = MAX (y1, 0); + y1 = MIN (y1, buf->rect.y1 - buf->rect.y0); + + gdk_pixbuf_composite (priv->pixbuf, + dest_pixbuf, + x0, y0, + x1 - x0, y1 - y0, + render_affine[4] - buf->rect.x0, + render_affine[5] - buf->rect.y0, + render_affine[0], + render_affine[3], + GDK_INTERP_BILINEAR, + 255); + + g_object_unref (dest_pixbuf); + } + else if (gdk_pixbuf_get_has_alpha(priv->pixbuf)) + art_rgb_rgba_affine (buf->buf, + buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1, + buf->buf_rowstride, + gdk_pixbuf_get_pixels(priv->pixbuf), + gdk_pixbuf_get_width(priv->pixbuf), + gdk_pixbuf_get_height(priv->pixbuf), + gdk_pixbuf_get_rowstride(priv->pixbuf), + render_affine, + ART_FILTER_NEAREST, + NULL); + else + art_rgb_affine (buf->buf, + buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1, + buf->buf_rowstride, + gdk_pixbuf_get_pixels(priv->pixbuf), + gdk_pixbuf_get_width(priv->pixbuf), + gdk_pixbuf_get_height(priv->pixbuf), + gdk_pixbuf_get_rowstride(priv->pixbuf), + render_affine, + ART_FILTER_NEAREST, + NULL); + + buf->is_bg = 0; +} + + + +/* Point handler for the pixbuf canvas item */ +static double +gnome_canvas_pixbuf_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + double i2c[6], render_affine[6], inv[6]; + ArtPoint c, p; + int px, py; + double no_hit; + guchar *src; + GdkPixbuf *pixbuf; + + gcp = GNOME_CANVAS_PIXBUF (item); + priv = gcp->priv; + pixbuf = priv->pixbuf; + + *actual_item = item; + + no_hit = item->canvas->pixels_per_unit * 2 + 10; + + if (!priv->pixbuf) + return no_hit; + + gnome_canvas_item_i2c_affine (item, i2c); + compute_render_affine (gcp, render_affine, i2c); + art_affine_invert (inv, render_affine); + + c.x = cx; + c.y = cy; + art_affine_point (&p, &c, inv); + px = p.x; + py = p.y; + + if (px < 0 || px >= gdk_pixbuf_get_width (pixbuf) || + py < 0 || py >= gdk_pixbuf_get_height (pixbuf)) + return no_hit; + + if (!gdk_pixbuf_get_has_alpha (pixbuf)) + return 0.0; + + src = gdk_pixbuf_get_pixels (pixbuf) + + py * gdk_pixbuf_get_rowstride (pixbuf) + + px * gdk_pixbuf_get_n_channels (pixbuf); + + if (src[3] < 128) + return no_hit; + else + return 0.0; +} + + + +/* Bounds handler for the pixbuf canvas item */ +static void +gnome_canvas_pixbuf_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasPixbuf *gcp; + PixbufPrivate *priv; + double i2c[6], viewport_affine[6]; + ArtDRect rect; + + gcp = GNOME_CANVAS_PIXBUF (item); + priv = gcp->priv; + + if (!priv->pixbuf) { + *x1 = *y1 = *x2 = *y2 = 0.0; + return; + } + + rect.x0 = 0.0; + rect.x1 = gdk_pixbuf_get_width (priv->pixbuf); + + rect.y0 = 0.0; + rect.y1 = gdk_pixbuf_get_height (priv->pixbuf); + + gnome_canvas_item_i2c_affine (item, i2c); + compute_viewport_affine (gcp, viewport_affine, i2c); + art_drect_affine_transform (&rect, &rect, viewport_affine); + + *x1 = rect.x0; + *y1 = rect.y0; + *x2 = rect.x1; + *y2 = rect.y1; +} diff --git a/libgnomecanvas/gnome-canvas-pixbuf.h b/libgnomecanvas/gnome-canvas-pixbuf.h new file mode 100644 index 0000000000..0554ab8624 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-pixbuf.h @@ -0,0 +1,62 @@ +/* GNOME libraries - GdkPixbuf item for the GNOME canvas + * + * Copyright (C) 1999 The Free Software Foundation + * + * Author: Federico Mena-Quintero <federico@gimp.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GNOME_CANVAS_PIXBUF_H +#define GNOME_CANVAS_PIXBUF_H + + +#include <libgnomecanvas/gnome-canvas.h> + +G_BEGIN_DECLS + + + +#define GNOME_TYPE_CANVAS_PIXBUF (gnome_canvas_pixbuf_get_type ()) +#define GNOME_CANVAS_PIXBUF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_PIXBUF, GnomeCanvasPixbuf)) +#define GNOME_CANVAS_PIXBUF_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_PIXBUF, GnomeCanvasPixbufClass)) +#define GNOME_IS_CANVAS_PIXBUF(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_PIXBUF)) +#define GNOME_IS_CANVAS_PIXBUF_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_PIXBUF)) +#define GNOME_CANVAS_PIXBUF_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_PIXBUF, GnomeCanvasPixbufClass)) + + +typedef struct _GnomeCanvasPixbuf GnomeCanvasPixbuf; +typedef struct _GnomeCanvasPixbufClass GnomeCanvasPixbufClass; + +struct _GnomeCanvasPixbuf { + GnomeCanvasItem item; + + /* Private data */ + gpointer priv; +}; + +struct _GnomeCanvasPixbufClass { + GnomeCanvasItemClass parent_class; +}; + + +GType gnome_canvas_pixbuf_get_type (void) G_GNUC_CONST; + + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-polygon.c b/libgnomecanvas/gnome-canvas-polygon.c new file mode 100644 index 0000000000..53c7965a88 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-polygon.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Polygon item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + * Rusty Conover <rconover@bangtail.net> + */ + +#include <config.h> +#include <math.h> +#include <string.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> +#include "libgnomecanvas.h" + +#include "gnome-canvas-shape.h" + +#define NUM_STATIC_POINTS 256 /* Number of static points to use to avoid allocating arrays */ + +enum { + PROP_0, + PROP_POINTS +}; + +static void gnome_canvas_polygon_class_init (GnomeCanvasPolygonClass *class); +static void gnome_canvas_polygon_init (GnomeCanvasPolygon *poly); +static void gnome_canvas_polygon_destroy (GtkObject *object); +static void gnome_canvas_polygon_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_polygon_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_polygon_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); + +static GnomeCanvasItemClass *parent_class; + +GType +gnome_canvas_polygon_get_type (void) +{ + static GType polygon_type; + + if (!polygon_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasPolygonClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_polygon_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasPolygon), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_polygon_init, + NULL /* value_table */ + }; + + polygon_type = g_type_register_static (GNOME_TYPE_CANVAS_SHAPE, "GnomeCanvasPolygon", + &object_info, 0); + } + + return polygon_type; +} + +static void +gnome_canvas_polygon_class_init (GnomeCanvasPolygonClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_polygon_set_property; + gobject_class->get_property = gnome_canvas_polygon_get_property; + + g_object_class_install_property + (gobject_class, + PROP_POINTS, + g_param_spec_boxed ("points", NULL, NULL, + GNOME_TYPE_CANVAS_POINTS, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_polygon_destroy; + + item_class->update = gnome_canvas_polygon_update; +} + +static void +gnome_canvas_polygon_init (GnomeCanvasPolygon *poly) +{ + poly->path_def = NULL; +} + +static void +gnome_canvas_polygon_destroy (GtkObject *object) +{ + GnomeCanvasPolygon *poly; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_POLYGON (object)); + + poly = GNOME_CANVAS_POLYGON (object); + + /* remember, destroy can be run multiple times! */ + + if(poly->path_def) + gnome_canvas_path_def_unref(poly->path_def); + + poly->path_def = NULL; + + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +set_points (GnomeCanvasPolygon *poly, GnomeCanvasPoints *points) +{ + int i; + + + if (poly->path_def) + gnome_canvas_path_def_unref(poly->path_def); + + if (!points) { + poly->path_def = gnome_canvas_path_def_new(); + gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (poly), poly->path_def); + return; + } + + + /* Optomize the path def to the number of points */ + poly->path_def = gnome_canvas_path_def_new_sized(points->num_points+1); + +#if 0 + /* No need for explicit duplicate, as closepaths does it for us (Lauris) */ + /* See if we need to duplicate the first point */ + duplicate = ((points->coords[0] != points->coords[2 * points->num_points - 2]) + || (points->coords[1] != points->coords[2 * points->num_points - 1])); +#endif + + + gnome_canvas_path_def_moveto (poly->path_def, points->coords[0], points->coords[1]); + + for (i = 1; i < points->num_points; i++) { + gnome_canvas_path_def_lineto(poly->path_def, points->coords[i * 2], points->coords[(i * 2) + 1]); + } + + gnome_canvas_path_def_closepath (poly->path_def); + + gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (poly), poly->path_def); +} + + +static void +gnome_canvas_polygon_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasPolygon *poly; + GnomeCanvasPoints *points; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_POLYGON (object)); + + item = GNOME_CANVAS_ITEM (object); + poly = GNOME_CANVAS_POLYGON (object); + + switch (param_id) { + case PROP_POINTS: + points = g_value_get_boxed (value); + + set_points (poly, points); + + gnome_canvas_item_request_update (item); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + +static void +gnome_canvas_polygon_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_POLYGON (object)); + + switch (param_id) { + case PROP_POINTS: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + +static void +gnome_canvas_polygon_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + /* Since the path has already been defined just pass the update up. */ + + if (parent_class->update) + (* parent_class->update) (item, affine, clip_path, flags); +} diff --git a/libgnomecanvas/gnome-canvas-polygon.h b/libgnomecanvas/gnome-canvas-polygon.h new file mode 100644 index 0000000000..738f0f453d --- /dev/null +++ b/libgnomecanvas/gnome-canvas-polygon.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Polygon item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + * Rusty Conover <rconover@bangtail.net> + */ + +#ifndef GNOME_CANVAS_POLYGON_H +#define GNOME_CANVAS_POLYGON_H + + +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-shape.h> +#include <libgnomecanvas/gnome-canvas-path-def.h> + +G_BEGIN_DECLS + + +/* Polygon item for the canvas. A polygon is a bit different from rectangles and ellipses in that + * points inside it will always be considered "inside", even if the fill color is not set. If you + * want to have a hollow polygon, use a line item instead. + * + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * points GnomeCanvasPoints* RW Pointer to a GnomeCanvasPoints structure. + * This can be created by a call to + * gnome_canvas_points_new() (in gnome-canvas-util.h). + * X coordinates are in the even indices of the + * points->coords array, Y coordinates are in + * the odd indices. + */ + +#define GNOME_TYPE_CANVAS_POLYGON (gnome_canvas_polygon_get_type ()) +#define GNOME_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_POLYGON, GnomeCanvasPolygon)) +#define GNOME_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_POLYGON, GnomeCanvasPolygonClass)) +#define GNOME_IS_CANVAS_POLYGON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_POLYGON)) +#define GNOME_IS_CANVAS_POLYGON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_POLYGON)) +#define GNOME_CANVAS_POLYGON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_POLYGON, GnomeCanvasPolygonClass)) + + +typedef struct _GnomeCanvasPolygon GnomeCanvasPolygon; +typedef struct _GnomeCanvasPolygonClass GnomeCanvasPolygonClass; + +struct _GnomeCanvasPolygon { + GnomeCanvasShape item; + + GnomeCanvasPathDef *path_def; +}; + +struct _GnomeCanvasPolygonClass { + GnomeCanvasShapeClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_polygon_get_type (void) G_GNUC_CONST; + +G_END_DECLS +#endif diff --git a/libgnomecanvas/gnome-canvas-rect-ellipse.c b/libgnomecanvas/gnome-canvas-rect-ellipse.c new file mode 100644 index 0000000000..10136866ef --- /dev/null +++ b/libgnomecanvas/gnome-canvas-rect-ellipse.c @@ -0,0 +1,431 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Rectangle and ellipse item types for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Rusty Conover <rconover@bangtail.net> + */ + +#include <config.h> +#include <math.h> +#include "gnome-canvas-rect-ellipse.h" +#include "gnome-canvas-util.h" +#include "gnome-canvas-shape.h" + + +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_rgb_svp.h> + +/* Base class for rectangle and ellipse item types */ + +#define noVERBOSE + +enum { + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2 +}; + + +static void gnome_canvas_re_class_init (GnomeCanvasREClass *class); +static void gnome_canvas_re_init (GnomeCanvasRE *re); +static void gnome_canvas_re_destroy (GtkObject *object); +static void gnome_canvas_re_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_re_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_rect_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static void gnome_canvas_ellipse_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); + +static GnomeCanvasItemClass *re_parent_class; + + +GType +gnome_canvas_re_get_type (void) +{ + static GType re_type; + + if (!re_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasREClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_re_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasRE), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_re_init, + NULL /* value_table */ + }; + + re_type = g_type_register_static (GNOME_TYPE_CANVAS_SHAPE, "GnomeCanvasRE", + &object_info, 0); + } + + return re_type; +} + +static void +gnome_canvas_re_class_init (GnomeCanvasREClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + + re_parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_re_set_property; + gobject_class->get_property = gnome_canvas_re_get_property; + + g_object_class_install_property + (gobject_class, + PROP_X1, + g_param_spec_double ("x1", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y1, + g_param_spec_double ("y1", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_X2, + g_param_spec_double ("x2", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y2, + g_param_spec_double ("y2", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_re_destroy; +} + +static void +gnome_canvas_re_init (GnomeCanvasRE *re) +{ + re->x1 = 0.0; + re->y1 = 0.0; + re->x2 = 0.0; + re->y2 = 0.0; + re->path_dirty = 0; +} + +static void +gnome_canvas_re_destroy (GtkObject *object) +{ + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_RE (object)); + + if (GTK_OBJECT_CLASS (re_parent_class)->destroy) + (* GTK_OBJECT_CLASS (re_parent_class)->destroy) (object); +} + +static void +gnome_canvas_re_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasRE *re; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_RE (object)); + + item = GNOME_CANVAS_ITEM (object); + re = GNOME_CANVAS_RE (object); + + switch (param_id) { + case PROP_X1: + re->x1 = g_value_get_double (value); + re->path_dirty = 1; + gnome_canvas_item_request_update (item); + break; + + case PROP_Y1: + re->y1 = g_value_get_double (value); + re->path_dirty = 1; + gnome_canvas_item_request_update (item); + break; + + case PROP_X2: + re->x2 = g_value_get_double (value); + re->path_dirty = 1; + gnome_canvas_item_request_update (item); + break; + + case PROP_Y2: + re->y2 = g_value_get_double (value); + re->path_dirty = 1; + gnome_canvas_item_request_update (item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_re_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasRE *re; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_RE (object)); + + re = GNOME_CANVAS_RE (object); + + switch (param_id) { + case PROP_X1: + g_value_set_double (value, re->x1); + break; + + case PROP_Y1: + g_value_set_double (value, re->y1); + break; + + case PROP_X2: + g_value_set_double (value, re->x2); + break; + + case PROP_Y2: + g_value_set_double (value, re->y2); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +/* Rectangle item */ +static void gnome_canvas_rect_class_init (GnomeCanvasRectClass *class); + + + +GType +gnome_canvas_rect_get_type (void) +{ + static GType rect_type; + + if (!rect_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasRectClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_rect_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasRect), + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL, + NULL /* value_table */ + }; + + rect_type = g_type_register_static (GNOME_TYPE_CANVAS_RE, "GnomeCanvasRect", + &object_info, 0); + } + + return rect_type; +} + +static void +gnome_canvas_rect_class_init (GnomeCanvasRectClass *class) +{ + GnomeCanvasItemClass *item_class; + + item_class = (GnomeCanvasItemClass *) class; + + item_class->update = gnome_canvas_rect_update; +} + +static void +gnome_canvas_rect_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags) +{ GnomeCanvasRE *re; + + GnomeCanvasPathDef *path_def; + + re = GNOME_CANVAS_RE(item); + + if (re->path_dirty) { + path_def = gnome_canvas_path_def_new (); + + gnome_canvas_path_def_moveto(path_def, re->x1, re->y1); + gnome_canvas_path_def_lineto(path_def, re->x2, re->y1); + gnome_canvas_path_def_lineto(path_def, re->x2, re->y2); + gnome_canvas_path_def_lineto(path_def, re->x1, re->y2); + gnome_canvas_path_def_lineto(path_def, re->x1, re->y1); + gnome_canvas_path_def_closepath_current(path_def); + gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (item), path_def); + gnome_canvas_path_def_unref(path_def); + re->path_dirty = 0; + } + + if (re_parent_class->update) + (* re_parent_class->update) (item, affine, clip_path, flags); +} + +/* Ellipse item */ + + +static void gnome_canvas_ellipse_class_init (GnomeCanvasEllipseClass *class); + + +GType +gnome_canvas_ellipse_get_type (void) +{ + static GType ellipse_type; + + if (!ellipse_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasEllipseClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_ellipse_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasEllipse), + 0, /* n_preallocs */ + (GInstanceInitFunc) NULL, + NULL /* value_table */ + }; + + ellipse_type = g_type_register_static (GNOME_TYPE_CANVAS_RE, "GnomeCanvasEllipse", + &object_info, 0); + } + + return ellipse_type; +} + +static void +gnome_canvas_ellipse_class_init (GnomeCanvasEllipseClass *class) +{ + GnomeCanvasItemClass *item_class; + + item_class = (GnomeCanvasItemClass *) class; + + item_class->update = gnome_canvas_ellipse_update; +} + +#define N_PTS 90 + +static void +gnome_canvas_ellipse_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags) { + GnomeCanvasPathDef *path_def; + GnomeCanvasRE *re; + + re = GNOME_CANVAS_RE(item); + + if (re->path_dirty) { + gdouble cx, cy, rx, ry; + gdouble beta = 0.26521648983954400922; /* 4*(1-cos(pi/8))/(3*sin(pi/8)) */ + gdouble sincosA = 0.70710678118654752440; /* sin (pi/4), cos (pi/4) */ + gdouble dx1, dy1, dx2, dy2; + gdouble mx, my; + + path_def = gnome_canvas_path_def_new(); + + cx = (re->x2 + re->x1) * 0.5; + cy = (re->y2 + re->y1) * 0.5; + rx = re->x2 - cx; + ry = re->y2 - cy; + + dx1 = beta * rx; + dy1 = beta * ry; + dx2 = beta * rx * sincosA; + dy2 = beta * ry * sincosA; + mx = rx * sincosA; + my = ry * sincosA; + + gnome_canvas_path_def_moveto (path_def, cx + rx, cy); + gnome_canvas_path_def_curveto (path_def, + cx + rx, cy - dy1, + cx + mx + dx2, cy - my + dy2, + cx + mx, cy - my); + gnome_canvas_path_def_curveto (path_def, + cx + mx - dx2, cy - my - dy2, + cx + dx1, cy - ry, + cx, cy - ry); + gnome_canvas_path_def_curveto (path_def, + cx - dx1, cy - ry, + cx - mx + dx2, cy - my - dy2, + cx - mx, cy - my); + gnome_canvas_path_def_curveto (path_def, + cx - mx - dx2, cy - my + dy2, + cx - rx, cy - dy1, + cx - rx, cy); + + gnome_canvas_path_def_curveto (path_def, + cx - rx, cy + dy1, + cx - mx - dx2, cy + my - dy2, + cx - mx, cy + my); + gnome_canvas_path_def_curveto (path_def, + cx - mx + dx2, cy + my + dy2, + cx - dx1, cy + ry, + cx, cy + ry); + gnome_canvas_path_def_curveto (path_def, + cx + dx1, cy + ry, + cx + mx - dx2, cy + my + dy2, + cx + mx, cy + my); + gnome_canvas_path_def_curveto (path_def, + cx + mx + dx2, cy + my - dy2, + cx + rx, cy + dy1, + cx + rx, cy); + + gnome_canvas_path_def_closepath_current(path_def); + + gnome_canvas_shape_set_path_def (GNOME_CANVAS_SHAPE (item), path_def); + gnome_canvas_path_def_unref(path_def); + re->path_dirty = 0; + } + + if (re_parent_class->update) + (* re_parent_class->update) (item, affine, clip_path, flags); +} diff --git a/libgnomecanvas/gnome-canvas-rect-ellipse.h b/libgnomecanvas/gnome-canvas-rect-ellipse.h new file mode 100644 index 0000000000..494e6cf485 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-rect-ellipse.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Rectangle and ellipse item types for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#ifndef GNOME_CANVAS_RECT_ELLIPSE_H +#define GNOME_CANVAS_RECT_ELLIPSE_H + + +#include <libgnomecanvas/gnome-canvas.h> + +#include <libgnomecanvas/gnome-canvas-shape.h> + +#include <libart_lgpl/art_svp.h> + +G_BEGIN_DECLS + + +/* Base class for rectangle and ellipse item types. These are defined by their top-left and + * bottom-right corners. Rectangles and ellipses share the following arguments: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * x1 double RW Leftmost coordinate of rectangle or ellipse + * y1 double RW Topmost coordinate of rectangle or ellipse + * x2 double RW Rightmost coordinate of rectangle or ellipse + * y2 double RW Bottommost coordinate of rectangle or ellipse + * fill_color string W X color specification for fill color, + * or NULL pointer for no color (transparent) + * fill_color_gdk GdkColor* RW Allocated GdkColor for fill + * outline_color string W X color specification for outline color, + * or NULL pointer for no color (transparent) + * outline_color_gdk GdkColor* RW Allocated GdkColor for outline + * fill_stipple GdkBitmap* RW Stipple pattern for fill + * outline_stipple GdkBitmap* RW Stipple pattern for outline + * width_pixels uint RW Width of the outline in pixels. The outline will + * not be scaled when the canvas zoom factor is changed. + * width_units double RW Width of the outline in canvas units. The outline + * will be scaled when the canvas zoom factor is changed. + */ + + +#define GNOME_TYPE_CANVAS_RE (gnome_canvas_re_get_type ()) +#define GNOME_CANVAS_RE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_RE, GnomeCanvasRE)) +#define GNOME_CANVAS_RE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_RE, GnomeCanvasREClass)) +#define GNOME_IS_CANVAS_RE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_RE)) +#define GNOME_IS_CANVAS_RE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_RE)) +#define GNOME_CANVAS_RE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_RE, GnomeCanvasREClass)) + + +typedef struct _GnomeCanvasRE GnomeCanvasRE; +typedef struct _GnomeCanvasREClass GnomeCanvasREClass; + +struct _GnomeCanvasRE { + GnomeCanvasShape item; + + double x1, y1, x2, y2; /* Corners of item */ + + unsigned int path_dirty : 1; +}; + +struct _GnomeCanvasREClass { + GnomeCanvasShapeClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_re_get_type (void) G_GNUC_CONST; + + +/* Rectangle item. No configurable or queryable arguments are available (use those in + * GnomeCanvasRE). + */ + + +#define GNOME_TYPE_CANVAS_RECT (gnome_canvas_rect_get_type ()) +#define GNOME_CANVAS_RECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_RECT, GnomeCanvasRect)) +#define GNOME_CANVAS_RECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_RECT, GnomeCanvasRectClass)) +#define GNOME_IS_CANVAS_RECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_RECT)) +#define GNOME_IS_CANVAS_RECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_RECT)) +#define GNOME_CANVAS_RECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_RECT, GnomeCanvasRectClass)) + + +typedef struct _GnomeCanvasRect GnomeCanvasRect; +typedef struct _GnomeCanvasRectClass GnomeCanvasRectClass; + +struct _GnomeCanvasRect { + GnomeCanvasRE re; +}; + +struct _GnomeCanvasRectClass { + GnomeCanvasREClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_rect_get_type (void) G_GNUC_CONST; + + +/* Ellipse item. No configurable or queryable arguments are available (use those in + * GnomeCanvasRE). + */ + + +#define GNOME_TYPE_CANVAS_ELLIPSE (gnome_canvas_ellipse_get_type ()) +#define GNOME_CANVAS_ELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_ELLIPSE, GnomeCanvasEllipse)) +#define GNOME_CANVAS_ELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ELLIPSE, GnomeCanvasEllipseClass)) +#define GNOME_IS_CANVAS_ELLIPSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_ELLIPSE)) +#define GNOME_IS_CANVAS_ELLIPSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ELLIPSE)) +#define GNOME_CANVAS_ELLIPSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_ELLIPSE, GnomeCanvasEllipseClass)) + + +typedef struct _GnomeCanvasEllipse GnomeCanvasEllipse; +typedef struct _GnomeCanvasEllipseClass GnomeCanvasEllipseClass; + +struct _GnomeCanvasEllipse { + GnomeCanvasRE re; +}; + +struct _GnomeCanvasEllipseClass { + GnomeCanvasREClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_ellipse_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-rich-text.c b/libgnomecanvas/gnome-canvas-rich-text.c new file mode 100644 index 0000000000..2a50f2ed4d --- /dev/null +++ b/libgnomecanvas/gnome-canvas-rich-text.c @@ -0,0 +1,2202 @@ +/* Editable GnomeCanvas text item based on GtkTextLayout, borrowed heavily + * from GtkTextView. + * + * Copyright (c) 2000 Red Hat, Inc. + * Copyright (c) 2001 Joe Shaw + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#define GTK_TEXT_USE_INTERNAL_UNSUPPORTED_API +#include <gtk/gtktextdisplay.h> + +#include "gnome-canvas.h" +#include "gnome-canvas-util.h" +#include "gnome-canvas-rich-text.h" +#include "gnome-canvas-i18n.h" + +struct _GnomeCanvasRichTextPrivate { + GtkTextLayout *layout; + GtkTextBuffer *buffer; + + char *text; + + /* Position at anchor */ + double x, y; + /* Dimensions */ + double width, height; + /* Top-left canvas coordinates for text */ + int cx, cy; + + gboolean cursor_visible; + gboolean cursor_blink; + gboolean editable; + gboolean visible; + gboolean grow_height; + GtkWrapMode wrap_mode; + GtkJustification justification; + GtkTextDirection direction; + GtkAnchorType anchor; + int pixels_above_lines; + int pixels_below_lines; + int pixels_inside_wrap; + int left_margin; + int right_margin; + int indent; + + guint preblink_timeout; + guint blink_timeout; + + guint selection_drag_handler; + + gint drag_start_x; + gint drag_start_y; + + gboolean just_selected_element; + + int clicks; + guint click_timeout; +}; + +enum { + PROP_0, + PROP_TEXT, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_EDITABLE, + PROP_VISIBLE, + PROP_CURSOR_VISIBLE, + PROP_CURSOR_BLINK, + PROP_GROW_HEIGHT, + PROP_WRAP_MODE, + PROP_JUSTIFICATION, + PROP_DIRECTION, + PROP_ANCHOR, + PROP_PIXELS_ABOVE_LINES, + PROP_PIXELS_BELOW_LINES, + PROP_PIXELS_INSIDE_WRAP, + PROP_LEFT_MARGIN, + PROP_RIGHT_MARGIN, + PROP_INDENT +}; + +enum { + TAG_CHANGED, + LAST_SIGNAL +}; + +static GnomeCanvasItemClass *parent_class; +static guint signals[LAST_SIGNAL] = { 0 }; + +static void gnome_canvas_rich_text_class_init(GnomeCanvasRichTextClass *klass); +static void gnome_canvas_rich_text_init(GnomeCanvasRichText *text); +static void gnome_canvas_rich_text_set_property(GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec); +static void gnome_canvas_rich_text_get_property(GObject *object, guint property_id, + GValue *value, GParamSpec *pspec); +static void gnome_canvas_rich_text_update(GnomeCanvasItem *item, double *affine, + ArtSVP *clip_path, int flags); +static void gnome_canvas_rich_text_realize(GnomeCanvasItem *item); +static void gnome_canvas_rich_text_unrealize(GnomeCanvasItem *item); +static double gnome_canvas_rich_text_point(GnomeCanvasItem *item, + double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item); +static void gnome_canvas_rich_text_draw(GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, int width, int height); +static void gnome_canvas_rich_text_render(GnomeCanvasItem *item, + GnomeCanvasBuf *buf); +static gint gnome_canvas_rich_text_event(GnomeCanvasItem *item, + GdkEvent *event); +static void gnome_canvas_rich_text_get_bounds(GnomeCanvasItem *text, double *px1, double *py1, + double *px2, double *py2); + +static void gnome_canvas_rich_text_ensure_layout(GnomeCanvasRichText *text); +static void gnome_canvas_rich_text_destroy_layout(GnomeCanvasRichText *text); +static void gnome_canvas_rich_text_start_cursor_blink(GnomeCanvasRichText *text, gboolean delay); +static void gnome_canvas_rich_text_stop_cursor_blink(GnomeCanvasRichText *text); +static void gnome_canvas_rich_text_move_cursor(GnomeCanvasRichText *text, + GtkMovementStep step, + gint count, + gboolean extend_selection); + + + +static GtkTextBuffer *get_buffer(GnomeCanvasRichText *text); +static gint blink_cb(gpointer data); + +#define PREBLINK_TIME 300 +#define CURSOR_ON_TIME 800 +#define CURSOR_OFF_TIME 400 + +GType +gnome_canvas_rich_text_get_type(void) +{ + static GType rich_text_type; + + if (!rich_text_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasRichTextClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_rich_text_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasRichText), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_rich_text_init, + NULL /* value_table */ + }; + + rich_text_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasRichText", + &object_info, 0); + } + + return rich_text_type; +} + +static void +gnome_canvas_rich_text_finalize(GObject *object) +{ + GnomeCanvasRichText *text; + + text = GNOME_CANVAS_RICH_TEXT(object); + + g_free (text->_priv); + text->_priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gnome_canvas_rich_text_class_init(GnomeCanvasRichTextClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GtkObjectClass *object_class = GTK_OBJECT_CLASS(klass); + GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS(klass); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gnome_canvas_rich_text_set_property; + gobject_class->get_property = gnome_canvas_rich_text_get_property; + + g_object_class_install_property ( + gobject_class, + PROP_TEXT, + g_param_spec_string ("text", + _("Text"), + _("Text to display"), + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_X, + g_param_spec_double ("x", + _("X"), + _("X position"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_Y, + g_param_spec_double ("y", + _("Y"), + _("Y position"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_WIDTH, + g_param_spec_double ("width", + _("Width"), + _("Width for text box"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_HEIGHT, + g_param_spec_double ("height", + _("Height"), + _("Height for text box"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_EDITABLE, + g_param_spec_boolean ("editable", + _("Editable"), + _("Is this rich text item editable?"), + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_VISIBLE, + g_param_spec_boolean ("visible", + _("Visible"), + _("Is this rich text item visible?"), + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_CURSOR_VISIBLE, + g_param_spec_boolean ("cursor_visible", + _("Cursor Visible"), + _("Is the cursor visible in this rich text item?"), + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_CURSOR_BLINK, + g_param_spec_boolean ("cursor_blink", + _("Cursor Blink"), + _("Does the cursor blink in this rich text item?"), + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_GROW_HEIGHT, + g_param_spec_boolean ("grow_height", + _("Grow Height"), + _("Should the text box height grow if the text does not fit?"), + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_WRAP_MODE, + g_param_spec_enum ("wrap_mode", + _("Wrap Mode"), + _("Wrap mode for multiline text"), + GTK_TYPE_WRAP_MODE, + GTK_WRAP_WORD, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_JUSTIFICATION, + g_param_spec_enum ("justification", + _("Justification"), + _("Justification mode"), + GTK_TYPE_JUSTIFICATION, + GTK_JUSTIFY_LEFT, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_DIRECTION, + g_param_spec_enum ("direction", + _("Direction"), + _("Text direction"), + GTK_TYPE_DIRECTION_TYPE, + gtk_widget_get_default_direction (), + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_ANCHOR, + g_param_spec_enum ("anchor", + _("Anchor"), + _("Anchor point for text"), + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_NW, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_PIXELS_ABOVE_LINES, + g_param_spec_int ("pixels_above_lines", + _("Pixels Above Lines"), + _("Number of pixels to put above lines"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_PIXELS_BELOW_LINES, + g_param_spec_int ("pixels_below_lines", + _("Pixels Below Lines"), + _("Number of pixels to put below lines"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_PIXELS_INSIDE_WRAP, + g_param_spec_int ("pixels_inside_wrap", + _("Pixels Inside Wrap"), + _("Number of pixels to put inside the wrap"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_LEFT_MARGIN, + g_param_spec_int ("left_margin", + _("Left Margin"), + _("Number of pixels in the left margin"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_RIGHT_MARGIN, + g_param_spec_int ("right_margin", + _("Right Margin"), + _("Number of pixels in the right margin"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + gobject_class, + PROP_INDENT, + g_param_spec_int ("indent", + _("Indentation"), + _("Number of pixels for indentation"), + G_MININT, G_MAXINT, + 0, + G_PARAM_READWRITE)); + + /* Signals */ + signals[TAG_CHANGED] = g_signal_new( + "tag_changed", + G_TYPE_FROM_CLASS(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GnomeCanvasRichTextClass, tag_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_OBJECT); + + gobject_class->finalize = gnome_canvas_rich_text_finalize; + + item_class->update = gnome_canvas_rich_text_update; + item_class->realize = gnome_canvas_rich_text_realize; + item_class->unrealize = gnome_canvas_rich_text_unrealize; + item_class->draw = gnome_canvas_rich_text_draw; + item_class->point = gnome_canvas_rich_text_point; + item_class->render = gnome_canvas_rich_text_render; + item_class->event = gnome_canvas_rich_text_event; + item_class->bounds = gnome_canvas_rich_text_get_bounds; +} /* gnome_canvas_rich_text_class_init */ + +static void +gnome_canvas_rich_text_init(GnomeCanvasRichText *text) +{ +#if 0 + GtkObject *object = GTK_OBJECT(text); + + object->flags |= GNOME_CANVAS_ITEM_ALWAYS_REDRAW; +#endif + text->_priv = g_new0(GnomeCanvasRichTextPrivate, 1); + + /* Try to set some sane defaults */ + text->_priv->cursor_visible = TRUE; + text->_priv->cursor_blink = TRUE; + text->_priv->editable = TRUE; + text->_priv->visible = TRUE; + text->_priv->grow_height = FALSE; + text->_priv->wrap_mode = GTK_WRAP_WORD; + text->_priv->justification = GTK_JUSTIFY_LEFT; + text->_priv->direction = gtk_widget_get_default_direction(); + text->_priv->anchor = GTK_ANCHOR_NW; + + text->_priv->blink_timeout = 0; + text->_priv->preblink_timeout = 0; + + text->_priv->clicks = 0; + text->_priv->click_timeout = 0; +} /* gnome_canvas_rich_text_init */ + +static void +gnome_canvas_rich_text_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(object); + + switch (property_id) { + case PROP_TEXT: + if (text->_priv->text) + g_free(text->_priv->text); + + text->_priv->text = g_value_dup_string (value); + + gtk_text_buffer_set_text( + get_buffer(text), text->_priv->text, strlen(text->_priv->text)); + + break; + case PROP_X: + text->_priv->x = g_value_get_double (value); + break; + case PROP_Y: + text->_priv->y = g_value_get_double (value); + break; + case PROP_WIDTH: + text->_priv->width = g_value_get_double (value); + break; + case PROP_HEIGHT: + text->_priv->height = g_value_get_double (value); + break; + case PROP_EDITABLE: + text->_priv->editable = g_value_get_boolean (value); + if (text->_priv->layout) { + text->_priv->layout->default_style->editable = + text->_priv->editable; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_VISIBLE: + text->_priv->visible = g_value_get_boolean (value); + if (text->_priv->layout) { + text->_priv->layout->default_style->invisible = + !text->_priv->visible; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_CURSOR_VISIBLE: + text->_priv->cursor_visible = g_value_get_boolean (value); + if (text->_priv->layout) { + gtk_text_layout_set_cursor_visible( + text->_priv->layout, text->_priv->cursor_visible); + + if (text->_priv->cursor_visible && text->_priv->cursor_blink) { + gnome_canvas_rich_text_start_cursor_blink( + text, FALSE); + } + else + gnome_canvas_rich_text_stop_cursor_blink(text); + } + break; + case PROP_CURSOR_BLINK: + text->_priv->cursor_blink = g_value_get_boolean (value); + if (text->_priv->layout && text->_priv->cursor_visible) { + if (text->_priv->cursor_blink && !text->_priv->blink_timeout) { + gnome_canvas_rich_text_start_cursor_blink( + text, FALSE); + } + else if (!text->_priv->cursor_blink && text->_priv->blink_timeout) { + gnome_canvas_rich_text_stop_cursor_blink(text); + gtk_text_layout_set_cursor_visible( + text->_priv->layout, TRUE); + } + } + break; + case PROP_GROW_HEIGHT: + text->_priv->grow_height = g_value_get_boolean (value); + /* FIXME: Recalc here */ + break; + case PROP_WRAP_MODE: + text->_priv->wrap_mode = g_value_get_enum (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->wrap_mode = + text->_priv->wrap_mode; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_JUSTIFICATION: + text->_priv->justification = g_value_get_enum (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->justification = + text->_priv->justification; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_DIRECTION: + text->_priv->direction = g_value_get_enum (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->direction = + text->_priv->direction; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_ANCHOR: + text->_priv->anchor = g_value_get_enum (value); + break; + case PROP_PIXELS_ABOVE_LINES: + text->_priv->pixels_above_lines = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->pixels_above_lines = + text->_priv->pixels_above_lines; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_PIXELS_BELOW_LINES: + text->_priv->pixels_below_lines = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->pixels_below_lines = + text->_priv->pixels_below_lines; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_PIXELS_INSIDE_WRAP: + text->_priv->pixels_inside_wrap = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->pixels_inside_wrap = + text->_priv->pixels_inside_wrap; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_LEFT_MARGIN: + text->_priv->left_margin = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->left_margin = + text->_priv->left_margin; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_RIGHT_MARGIN: + text->_priv->right_margin = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->right_margin = + text->_priv->right_margin; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + case PROP_INDENT: + text->_priv->pixels_above_lines = g_value_get_int (value); + + if (text->_priv->layout) { + text->_priv->layout->default_style->indent = text->_priv->indent; + gtk_text_layout_default_style_changed(text->_priv->layout); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(text)); +} + +static void +gnome_canvas_rich_text_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(object); + + switch (property_id) { + case PROP_TEXT: + g_value_set_string (value, text->_priv->text); + break; + case PROP_X: + g_value_set_double (value, text->_priv->x); + break; + case PROP_Y: + g_value_set_double (value, text->_priv->y); + break; + case PROP_HEIGHT: + g_value_set_double (value, text->_priv->height); + break; + case PROP_WIDTH: + g_value_set_double (value, text->_priv->width); + break; + case PROP_EDITABLE: + g_value_set_boolean (value, text->_priv->editable); + break; + case PROP_CURSOR_VISIBLE: + g_value_set_boolean (value, text->_priv->cursor_visible); + break; + case PROP_CURSOR_BLINK: + g_value_set_boolean (value, text->_priv->cursor_blink); + break; + case PROP_GROW_HEIGHT: + g_value_set_boolean (value, text->_priv->grow_height); + break; + case PROP_WRAP_MODE: + g_value_set_enum (value, text->_priv->wrap_mode); + break; + case PROP_JUSTIFICATION: + g_value_set_enum (value, text->_priv->justification); + break; + case PROP_DIRECTION: + g_value_set_enum (value, text->_priv->direction); + break; + case PROP_ANCHOR: + g_value_set_enum (value, text->_priv->anchor); + break; + case PROP_PIXELS_ABOVE_LINES: + g_value_set_enum (value, text->_priv->pixels_above_lines); + break; + case PROP_PIXELS_BELOW_LINES: + g_value_set_int (value, text->_priv->pixels_below_lines); + break; + case PROP_PIXELS_INSIDE_WRAP: + g_value_set_int (value, text->_priv->pixels_inside_wrap); + break; + case PROP_LEFT_MARGIN: + g_value_set_int (value, text->_priv->left_margin); + break; + case PROP_RIGHT_MARGIN: + g_value_set_int (value, text->_priv->right_margin); + break; + case PROP_INDENT: + g_value_set_int (value, text->_priv->indent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gnome_canvas_rich_text_realize(GnomeCanvasItem *item) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + + (* GNOME_CANVAS_ITEM_CLASS(parent_class)->realize)(item); + + gnome_canvas_rich_text_ensure_layout(text); +} /* gnome_canvas_rich_text_realize */ + +static void +gnome_canvas_rich_text_unrealize(GnomeCanvasItem *item) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + + gnome_canvas_rich_text_destroy_layout(text); + + (* GNOME_CANVAS_ITEM_CLASS(parent_class)->unrealize)(item); +} /* gnome_canvas_rich_text_unrealize */ + +static void +gnome_canvas_rich_text_move_iter_by_lines(GnomeCanvasRichText *text, + GtkTextIter *newplace, gint count) +{ + while (count < 0) { + gtk_text_layout_move_iter_to_previous_line( + text->_priv->layout, newplace); + count++; + } + + while (count > 0) { + gtk_text_layout_move_iter_to_next_line( + text->_priv->layout, newplace); + count--; + } +} /* gnome_canvas_rich_text_move_iter_by_lines */ + +static gint +gnome_canvas_rich_text_get_cursor_x_position(GnomeCanvasRichText *text) +{ + GtkTextIter insert; + GdkRectangle rect; + + gtk_text_buffer_get_iter_at_mark( + get_buffer(text), &insert, + gtk_text_buffer_get_mark(get_buffer(text), "insert")); + gtk_text_layout_get_cursor_locations( + text->_priv->layout, &insert, &rect, NULL); + + return rect.x; +} /* gnome_canvas_rich_text_get_cursor_x_position */ + +static void +gnome_canvas_rich_text_move_cursor(GnomeCanvasRichText *text, + GtkMovementStep step, + gint count, gboolean extend_selection) +{ + GtkTextIter insert, newplace; + + gtk_text_buffer_get_iter_at_mark( + get_buffer(text), &insert, + gtk_text_buffer_get_mark(get_buffer(text), "insert")); + + newplace = insert; + + switch (step) { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + gtk_text_iter_forward_cursor_positions(&newplace, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + gtk_text_layout_move_iter_visually( + text->_priv->layout, &newplace, count); + break; + case GTK_MOVEMENT_WORDS: + if (count < 0) + gtk_text_iter_backward_word_starts(&newplace, -count); + else if (count > 0) + gtk_text_iter_forward_word_ends(&newplace, count); + break; + case GTK_MOVEMENT_DISPLAY_LINES: + gnome_canvas_rich_text_move_iter_by_lines( + text, &newplace, count); + gtk_text_layout_move_iter_to_x( + text->_priv->layout, &newplace, + gnome_canvas_rich_text_get_cursor_x_position(text)); + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + if (count > 1) { + gnome_canvas_rich_text_move_iter_by_lines( + text, &newplace, --count); + } + else if (count < -1) { + gnome_canvas_rich_text_move_iter_by_lines( + text, &newplace, ++count); + } + + if (count != 0) { + gtk_text_layout_move_iter_to_line_end( + text->_priv->layout, &newplace, count); + } + break; + case GTK_MOVEMENT_PARAGRAPHS: + /* FIXME: Busted in gtktextview.c too */ + break; + case GTK_MOVEMENT_PARAGRAPH_ENDS: + if (count > 0) + gtk_text_iter_forward_to_line_end(&newplace); + else if (count < 0) + gtk_text_iter_set_line_offset(&newplace, 0); + break; + case GTK_MOVEMENT_BUFFER_ENDS: + if (count > 0) { + gtk_text_buffer_get_end_iter( + get_buffer(text), &newplace); + } + else if (count < 0) { + gtk_text_buffer_get_iter_at_offset( + get_buffer(text), &newplace, 0); + } + break; + default: + break; + } + + if (!gtk_text_iter_equal(&insert, &newplace)) { + if (extend_selection) { + gtk_text_buffer_move_mark( + get_buffer(text), + gtk_text_buffer_get_mark( + get_buffer(text), "insert"), + &newplace); + } + else { + gtk_text_buffer_place_cursor( + get_buffer(text), &newplace); + } + } + + gnome_canvas_rich_text_start_cursor_blink(text, TRUE); +} /* gnome_canvas_rich_text_move_cursor */ + +static gboolean +whitespace(gunichar ch, gpointer user_data) +{ + return (ch == ' ' || ch == '\t'); +} /* whitespace */ + +static gboolean +not_whitespace(gunichar ch, gpointer user_data) +{ + return !whitespace(ch, user_data); +} /* not_whitespace */ + +static gboolean +find_whitespace_region(const GtkTextIter *center, + GtkTextIter *start, GtkTextIter *end) +{ + *start = *center; + *end = *center; + + if (gtk_text_iter_backward_find_char(start, not_whitespace, NULL, NULL)) + gtk_text_iter_forward_char(start); + if (whitespace(gtk_text_iter_get_char(end), NULL)) + gtk_text_iter_forward_find_char(end, not_whitespace, NULL, NULL); + + return !gtk_text_iter_equal(start, end); +} /* find_whitespace_region */ + +static void +gnome_canvas_rich_text_delete_from_cursor(GnomeCanvasRichText *text, + GtkDeleteType type, + gint count) +{ + GtkTextIter insert, start, end; + + /* Special case: If the user wants to delete a character and there is + a selection, then delete the selection and return */ + if (type == GTK_DELETE_CHARS) { + if (gtk_text_buffer_delete_selection(get_buffer(text), TRUE, + text->_priv->editable)) + return; + } + + gtk_text_buffer_get_iter_at_mark( + get_buffer(text), &insert, + gtk_text_buffer_get_mark(get_buffer(text), "insert")); + + start = insert; + end = insert; + + switch (type) { + case GTK_DELETE_CHARS: + gtk_text_iter_forward_cursor_positions(&end, count); + break; + case GTK_DELETE_WORD_ENDS: + if (count > 0) + gtk_text_iter_forward_word_ends(&end, count); + else if (count < 0) + gtk_text_iter_backward_word_starts(&start, -count); + break; + case GTK_DELETE_WORDS: + break; + case GTK_DELETE_DISPLAY_LINE_ENDS: + break; + case GTK_DELETE_PARAGRAPH_ENDS: + if (gtk_text_iter_ends_line(&end)) { + gtk_text_iter_forward_line(&end); + --count; + } + + while (count > 0) { + if (!gtk_text_iter_forward_to_line_end(&end)) + break; + + --count; + } + break; + case GTK_DELETE_PARAGRAPHS: + if (count > 0) { + gtk_text_iter_set_line_offset(&start, 0); + gtk_text_iter_forward_to_line_end(&end); + + /* Do the lines beyond the first. */ + while (count > 1) { + gtk_text_iter_forward_to_line_end(&end); + --count; + } + } + break; + case GTK_DELETE_WHITESPACE: + find_whitespace_region(&insert, &start, &end); + break; + + default: + break; + } + + if (!gtk_text_iter_equal(&start, &end)) { + gtk_text_buffer_begin_user_action(get_buffer(text)); + gtk_text_buffer_delete_interactive( + get_buffer(text), &start, &end, text->_priv->editable); + gtk_text_buffer_end_user_action(get_buffer(text)); + } +} /* gnome_canvas_rich_text_delete_from_cursor */ + +static gint +selection_motion_event_handler(GnomeCanvasRichText *text, GdkEvent *event, + gpointer data) +{ + GtkTextIter newplace; + GtkTextMark *mark; + double newx, newy; + + /* We only want to handle motion events... */ + if (event->type != GDK_MOTION_NOTIFY) + return FALSE; + + newx = (event->motion.x - text->_priv->x) * + GNOME_CANVAS_ITEM(text)->canvas->pixels_per_unit; + newy = (event->motion.y - text->_priv->y) * + GNOME_CANVAS_ITEM(text)->canvas->pixels_per_unit; + + gtk_text_layout_get_iter_at_pixel(text->_priv->layout, &newplace, newx, newy); + mark = gtk_text_buffer_get_mark(get_buffer(text), "insert"); + gtk_text_buffer_move_mark(get_buffer(text), mark, &newplace); + + return TRUE; +} /* selection_motion_event_handler */ + +static void +gnome_canvas_rich_text_start_selection_drag(GnomeCanvasRichText *text, + const GtkTextIter *iter, + GdkEventButton *button) +{ + GtkTextIter newplace; + + g_return_if_fail(text->_priv->selection_drag_handler == 0); + +#if 0 + gnome_canvas_item_grab_focus(GNOME_CANVAS_ITEM(text)); +#endif + + newplace = *iter; + + gtk_text_buffer_place_cursor(get_buffer(text), &newplace); + + text->_priv->selection_drag_handler = g_signal_connect( + text, "event", + G_CALLBACK (selection_motion_event_handler), + NULL); +} /* gnome_canvas_rich_text_start_selection_drag */ + +static gboolean +gnome_canvas_rich_text_end_selection_drag(GnomeCanvasRichText *text, + GdkEventButton *event) +{ + if (text->_priv->selection_drag_handler == 0) + return FALSE; + + g_signal_handler_disconnect (text, text->_priv->selection_drag_handler); + text->_priv->selection_drag_handler = 0; + +#if 0 + gnome_canvas_item_grab(NULL); +#endif + + return TRUE; +} /* gnome_canvas_rich_text_end_selection_drag */ + +static void +gnome_canvas_rich_text_emit_tag_changed(GnomeCanvasRichText *text, + GtkTextTag *tag) +{ + g_signal_emit(G_OBJECT(text), signals[TAG_CHANGED], 0, tag); +} /* gnome_canvas_rich_text_emit_tag_changed */ + +static gint +gnome_canvas_rich_text_key_press_event(GnomeCanvasItem *item, + GdkEventKey *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + gboolean extend_selection = FALSE; + gboolean handled = FALSE; + +#if 0 + printf("Key press event\n"); +#endif + + if (!text->_priv->layout || !text->_priv->buffer) + return FALSE; + + if (event->state & GDK_SHIFT_MASK) + extend_selection = TRUE; + + switch (event->keyval) { + case GDK_Return: + case GDK_KP_Enter: + gtk_text_buffer_delete_selection( + get_buffer(text), TRUE, text->_priv->editable); + gtk_text_buffer_insert_interactive_at_cursor( + get_buffer(text), "\n", 1, text->_priv->editable); + handled = TRUE; + break; + + case GDK_Tab: + gtk_text_buffer_insert_interactive_at_cursor( + get_buffer(text), "\t", 1, text->_priv->editable); + handled = TRUE; + break; + + /* MOVEMENT */ + case GDK_Right: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_WORDS, 1, + extend_selection); + handled = TRUE; + } + else { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_VISUAL_POSITIONS, 1, + extend_selection); + handled = TRUE; + } + break; + case GDK_Left: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_WORDS, -1, + extend_selection); + handled = TRUE; + } + else { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_VISUAL_POSITIONS, -1, + extend_selection); + handled = TRUE; + } + break; + case GDK_f: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_LOGICAL_POSITIONS, 1, + extend_selection); + handled = TRUE; + } + else if (event->state & GDK_MOD1_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_WORDS, 1, + extend_selection); + handled = TRUE; + } + break; + case GDK_b: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_LOGICAL_POSITIONS, -1, + extend_selection); + handled = TRUE; + } + else if (event->state & GDK_MOD1_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_WORDS, -1, + extend_selection); + handled = TRUE; + } + break; + case GDK_Up: + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_DISPLAY_LINES, -1, + extend_selection); + handled = TRUE; + break; + case GDK_Down: + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_DISPLAY_LINES, 1, + extend_selection); + handled = TRUE; + break; + case GDK_p: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_DISPLAY_LINES, -1, + extend_selection); + handled = TRUE; + } + break; + case GDK_n: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_DISPLAY_LINES, 1, + extend_selection); + handled = TRUE; + } + break; + case GDK_Home: + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_PARAGRAPH_ENDS, -1, + extend_selection); + handled = TRUE; + break; + case GDK_End: + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_PARAGRAPH_ENDS, 1, + extend_selection); + handled = TRUE; + break; + case GDK_a: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_PARAGRAPH_ENDS, -1, + extend_selection); + handled = TRUE; + } + break; + case GDK_e: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_move_cursor( + text, GTK_MOVEMENT_PARAGRAPH_ENDS, 1, + extend_selection); + handled = TRUE; + } + break; + + /* DELETING TEXT */ + case GDK_Delete: + case GDK_KP_Delete: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_WORD_ENDS, 1); + handled = TRUE; + } + else { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_CHARS, 1); + handled = TRUE; + } + break; + case GDK_d: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_CHARS, 1); + handled = TRUE; + } + else if (event->state & GDK_MOD1_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_WORD_ENDS, 1); + handled = TRUE; + } + break; + case GDK_BackSpace: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_WORD_ENDS, -1); + handled = TRUE; + } + else { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_CHARS, -1); + } + handled = TRUE; + break; + case GDK_k: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_PARAGRAPH_ENDS, 1); + handled = TRUE; + } + break; + case GDK_u: + if (event->state & GDK_CONTROL_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_PARAGRAPHS, 1); + handled = TRUE; + } + break; + case GDK_space: + if (event->state & GDK_MOD1_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_WHITESPACE, 1); + handled = TRUE; + } + break; + case GDK_backslash: + if (event->state & GDK_MOD1_MASK) { + gnome_canvas_rich_text_delete_from_cursor( + text, GTK_DELETE_WHITESPACE, 1); + handled = TRUE; + } + break; + default: + break; + } + + /* An empty string, click just pressing "Alt" by itself or whatever. */ + if (!event->length) + return FALSE; + + if (!handled) { + gtk_text_buffer_delete_selection( + get_buffer(text), TRUE, text->_priv->editable); + gtk_text_buffer_insert_interactive_at_cursor( + get_buffer(text), event->string, event->length, + text->_priv->editable); + } + + gnome_canvas_rich_text_start_cursor_blink(text, TRUE); + + return TRUE; +} /* gnome_canvas_rich_text_key_press_event */ + +static gint +gnome_canvas_rich_text_key_release_event(GnomeCanvasItem *item, + GdkEventKey *event) +{ + return FALSE; +} /* gnome_canvas_rich_text_key_release_event */ + +static gboolean +_click(gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + + text->_priv->clicks = 0; + text->_priv->click_timeout = 0; + + return FALSE; +} /* _click */ + +static gint +gnome_canvas_rich_text_button_press_event(GnomeCanvasItem *item, + GdkEventButton *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + GtkTextIter iter; + GdkEventType event_type; + double newx, newy; + + newx = (event->x - text->_priv->x) * item->canvas->pixels_per_unit; + newy = (event->y - text->_priv->y) * item->canvas->pixels_per_unit; + + gtk_text_layout_get_iter_at_pixel(text->_priv->layout, &iter, newx, newy); + + /* The canvas doesn't give us double- or triple-click events, so + we have to synthesize them ourselves. Yay. */ + event_type = event->type; + if (event_type == GDK_BUTTON_PRESS) { + text->_priv->clicks++; + text->_priv->click_timeout = g_timeout_add(400, _click, text); + + if (text->_priv->clicks > 3) + text->_priv->clicks = text->_priv->clicks % 3; + + if (text->_priv->clicks == 1) + event_type = GDK_BUTTON_PRESS; + else if (text->_priv->clicks == 2) + event_type = GDK_2BUTTON_PRESS; + else if (text->_priv->clicks == 3) + event_type = GDK_3BUTTON_PRESS; + else + printf("ZERO CLICKS!\n"); + } + + if (event->button == 1 && event_type == GDK_BUTTON_PRESS) { + GtkTextIter start, end; + + if (gtk_text_buffer_get_selection_bounds( + get_buffer(text), &start, &end) && + gtk_text_iter_in_range(&iter, &start, &end)) { + text->_priv->drag_start_x = event->x; + text->_priv->drag_start_y = event->y; + } + else { + gnome_canvas_rich_text_start_selection_drag( + text, &iter, event); + } + + return TRUE; + } + else if (event->button == 1 && event_type == GDK_2BUTTON_PRESS) { + GtkTextIter start, end; + +#if 0 + printf("double-click\n"); +#endif + + gnome_canvas_rich_text_end_selection_drag(text, event); + + start = iter; + end = start; + + if (gtk_text_iter_inside_word(&start)) { + if (!gtk_text_iter_starts_word(&start)) + gtk_text_iter_backward_word_start(&start); + + if (!gtk_text_iter_ends_word(&end)) + gtk_text_iter_forward_word_end(&end); + } + + gtk_text_buffer_move_mark( + get_buffer(text), + gtk_text_buffer_get_selection_bound(get_buffer(text)), + &start); + gtk_text_buffer_move_mark( + get_buffer(text), + gtk_text_buffer_get_insert(get_buffer(text)), &end); + + text->_priv->just_selected_element = TRUE; + + return TRUE; + } + else if (event->button == 1 && event_type == GDK_3BUTTON_PRESS) { + GtkTextIter start, end; + +#if 0 + printf("triple-click\n"); +#endif + + gnome_canvas_rich_text_end_selection_drag(text, event); + + start = iter; + end = start; + + if (gtk_text_layout_iter_starts_line(text->_priv->layout, &start)) { + gtk_text_layout_move_iter_to_line_end( + text->_priv->layout, &start, -1); + } + else { + gtk_text_layout_move_iter_to_line_end( + text->_priv->layout, &start, -1); + + if (!gtk_text_layout_iter_starts_line( + text->_priv->layout, &end)) { + gtk_text_layout_move_iter_to_line_end( + text->_priv->layout, &end, 1); + } + } + + gtk_text_buffer_move_mark( + get_buffer(text), + gtk_text_buffer_get_selection_bound(get_buffer(text)), + &start); + gtk_text_buffer_move_mark( + get_buffer(text), + gtk_text_buffer_get_insert(get_buffer(text)), &end); + + text->_priv->just_selected_element = TRUE; + + return TRUE; + } + else if (event->button == 2 && event_type == GDK_BUTTON_PRESS) { + gtk_text_buffer_paste_clipboard( + get_buffer(text), + gtk_clipboard_get (GDK_SELECTION_PRIMARY), + &iter, text->_priv->editable); + } + + return FALSE; +} /* gnome_canvas_rich_text_button_press_event */ + +static gint +gnome_canvas_rich_text_button_release_event(GnomeCanvasItem *item, + GdkEventButton *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + double newx, newy; + + newx = (event->x - text->_priv->x) * item->canvas->pixels_per_unit; + newy = (event->y - text->_priv->y) * item->canvas->pixels_per_unit; + + if (event->button == 1) { + if (text->_priv->drag_start_x >= 0) { + text->_priv->drag_start_x = -1; + text->_priv->drag_start_y = -1; + } + + if (gnome_canvas_rich_text_end_selection_drag(text, event)) + return TRUE; + else if (text->_priv->just_selected_element) { + text->_priv->just_selected_element = FALSE; + return FALSE; + } + else { + GtkTextIter iter; + + gtk_text_layout_get_iter_at_pixel( + text->_priv->layout, &iter, newx, newy); + + gtk_text_buffer_place_cursor(get_buffer(text), &iter); + + return FALSE; + } + } + + return FALSE; +} /* gnome_canvas_rich_text_button_release_event */ + +static gint +gnome_canvas_rich_text_focus_in_event(GnomeCanvasItem *item, + GdkEventFocus *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + + if (text->_priv->cursor_visible && text->_priv->layout) { + gtk_text_layout_set_cursor_visible(text->_priv->layout, TRUE); + gnome_canvas_rich_text_start_cursor_blink(text, FALSE); + } + + return FALSE; +} /* gnome_canvas_rich_text_focus_in_event */ + +static gint +gnome_canvas_rich_text_focus_out_event(GnomeCanvasItem *item, + GdkEventFocus *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + + if (text->_priv->cursor_visible && text->_priv->layout) { + gtk_text_layout_set_cursor_visible(text->_priv->layout, FALSE); + gnome_canvas_rich_text_stop_cursor_blink(text); + } + + return FALSE; +} /* gnome_canvas_rich_text_focus_out_event */ + +static gboolean +get_event_coordinates(GdkEvent *event, gint *x, gint *y) +{ + g_return_val_if_fail(event, FALSE); + + switch (event->type) { + case GDK_MOTION_NOTIFY: + *x = event->motion.x; + *y = event->motion.y; + return TRUE; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + *x = event->button.x; + *y = event->button.y; + return TRUE; + + default: + return FALSE; + } +} /* get_event_coordinates */ + +static void +emit_event_on_tags(GnomeCanvasRichText *text, GdkEvent *event, + GtkTextIter *iter) +{ + GSList *tags; + GSList *i; + + tags = gtk_text_iter_get_tags(iter); + + i = tags; + while (i) { + GtkTextTag *tag = i->data; + + gtk_text_tag_event(tag, G_OBJECT(text), event, iter); + + /* The cursor has been moved to within this tag. Emit the + tag_changed signal */ + if (event->type == GDK_BUTTON_RELEASE || + event->type == GDK_KEY_PRESS || + event->type == GDK_KEY_RELEASE) { + gnome_canvas_rich_text_emit_tag_changed( + text, tag); + } + + i = g_slist_next(i); + } + + g_slist_free(tags); +} /* emit_event_on_tags */ + +static gint +gnome_canvas_rich_text_event(GnomeCanvasItem *item, GdkEvent *event) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + int x, y; + + if (get_event_coordinates(event, &x, &y)) { + GtkTextIter iter; + + x -= text->_priv->x; + y -= text->_priv->y; + + gtk_text_layout_get_iter_at_pixel(text->_priv->layout, &iter, x, y); + emit_event_on_tags(text, event, &iter); + } + else if (event->type == GDK_KEY_PRESS || + event->type == GDK_KEY_RELEASE) { + GtkTextMark *insert; + GtkTextIter iter; + + insert = gtk_text_buffer_get_mark(get_buffer(text), "insert"); + gtk_text_buffer_get_iter_at_mark( + get_buffer(text), &iter, insert); + emit_event_on_tags(text, event, &iter); + } + + switch (event->type) { + case GDK_KEY_PRESS: + return gnome_canvas_rich_text_key_press_event( + item, (GdkEventKey *) event); + case GDK_KEY_RELEASE: + return gnome_canvas_rich_text_key_release_event( + item, (GdkEventKey *) event); + case GDK_BUTTON_PRESS: + return gnome_canvas_rich_text_button_press_event( + item, (GdkEventButton *) event); + case GDK_BUTTON_RELEASE: + return gnome_canvas_rich_text_button_release_event( + item, (GdkEventButton *) event); + case GDK_FOCUS_CHANGE: + if (((GdkEventFocus *) event)->window != + item->canvas->layout.bin_window) + return FALSE; + + if (((GdkEventFocus *) event)->in) + return gnome_canvas_rich_text_focus_in_event( + item, (GdkEventFocus *) event); + else + return gnome_canvas_rich_text_focus_out_event( + item, (GdkEventFocus *) event); + default: + return FALSE; + } +} /* gnome_canvas_rich_text_event */ + +/* Cut/Copy/Paste */ + +/** + * gnome_canvas_rich_text_cut_clipboard: + * @text: a #GnomeCanvasRichText. + * + * Copies the currently selected @text to clipboard, then deletes said text + * if it's editable. + **/ +void +gnome_canvas_rich_text_cut_clipboard(GnomeCanvasRichText *text) +{ + g_return_if_fail(text); + g_return_if_fail(get_buffer(text)); + + gtk_text_buffer_cut_clipboard(get_buffer(text), + gtk_clipboard_get (GDK_SELECTION_PRIMARY), + text->_priv->editable); +} /* gnome_canvas_rich_text_cut_clipboard */ + + +/** + * gnome_canvas_rich_text_copy_clipboard: + * @text: a #GnomeCanvasRichText. + * + * Copies the currently selected @text to clipboard. + **/ +void +gnome_canvas_rich_text_copy_clipboard(GnomeCanvasRichText *text) +{ + g_return_if_fail(text); + g_return_if_fail(get_buffer(text)); + + gtk_text_buffer_copy_clipboard(get_buffer(text), + gtk_clipboard_get (GDK_SELECTION_PRIMARY)); +} /* gnome_canvas_rich_text_cut_clipboard */ + + +/** + * gnome_canvas_rich_text_paste_clipboard: + * @text: a #GnomeCanvasRichText. + * + * Pastes the contents of the clipboard at the insertion point. + **/ +void +gnome_canvas_rich_text_paste_clipboard(GnomeCanvasRichText *text) +{ + g_return_if_fail(text); + g_return_if_fail(get_buffer(text)); + + gtk_text_buffer_paste_clipboard(get_buffer(text), + gtk_clipboard_get (GDK_SELECTION_PRIMARY), + NULL, + text->_priv->editable); +} /* gnome_canvas_rich_text_cut_clipboard */ + +static gint +preblink_cb(gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + + text->_priv->preblink_timeout = 0; + gnome_canvas_rich_text_start_cursor_blink(text, FALSE); + + /* Remove ourselves */ + return FALSE; +} /* preblink_cb */ + +static gint +blink_cb(gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + gboolean visible; + + g_assert(text->_priv->layout); + g_assert(text->_priv->cursor_visible); + + visible = gtk_text_layout_get_cursor_visible(text->_priv->layout); + if (visible) + text->_priv->blink_timeout = g_timeout_add( + CURSOR_OFF_TIME, blink_cb, text); + else + text->_priv->blink_timeout = g_timeout_add( + CURSOR_ON_TIME, blink_cb, text); + + gtk_text_layout_set_cursor_visible(text->_priv->layout, !visible); + + /* Remove ourself */ + return FALSE; +} /* blink_cb */ + +static void +gnome_canvas_rich_text_start_cursor_blink(GnomeCanvasRichText *text, + gboolean with_delay) +{ + if (!text->_priv->layout) + return; + + if (!text->_priv->cursor_visible || !text->_priv->cursor_blink) + return; + + if (text->_priv->preblink_timeout != 0) { + g_source_remove(text->_priv->preblink_timeout); + text->_priv->preblink_timeout = 0; + } + + if (with_delay) { + if (text->_priv->blink_timeout != 0) { + g_source_remove(text->_priv->blink_timeout); + text->_priv->blink_timeout = 0; + } + + gtk_text_layout_set_cursor_visible(text->_priv->layout, TRUE); + + text->_priv->preblink_timeout = g_timeout_add( + PREBLINK_TIME, preblink_cb, text); + } + else { + if (text->_priv->blink_timeout == 0) { + gtk_text_layout_set_cursor_visible(text->_priv->layout, TRUE); + text->_priv->blink_timeout = g_timeout_add( + CURSOR_ON_TIME, blink_cb, text); + } + } +} /* gnome_canvas_rich_text_start_cursor_blink */ + +static void +gnome_canvas_rich_text_stop_cursor_blink(GnomeCanvasRichText *text) +{ + if (text->_priv->blink_timeout) { + g_source_remove(text->_priv->blink_timeout); + text->_priv->blink_timeout = 0; + } +} /* gnome_canvas_rich_text_stop_cursor_blink */ + +/* We have to request updates this way because the update cycle is not + re-entrant. This will fire off a request in an idle loop. */ +static gboolean +request_update(gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + + gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(text)); + + return FALSE; +} /* request_update */ + +static void +invalidated_handler(GtkTextLayout *layout, gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + +#if 0 + printf("Text is being invalidated.\n"); +#endif + + gtk_text_layout_validate(text->_priv->layout, 2000); + + /* We are called from the update cycle; gotta put this in an idle + loop. */ + g_idle_add(request_update, text); +} /* invalidated_handler */ + +static void +scale_fonts(GtkTextTag *tag, gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + + if (!tag->values) + return; + + g_object_set( + G_OBJECT(tag), "scale", + text->_priv->layout->default_style->font_scale, NULL); +} /* scale_fonts */ + +static void +changed_handler(GtkTextLayout *layout, gint start_y, + gint old_height, gint new_height, gpointer data) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(data); + +#if 0 + printf("Layout %p is being changed.\n", text->_priv->layout); +#endif + + if (text->_priv->layout->default_style->font_scale != + GNOME_CANVAS_ITEM(text)->canvas->pixels_per_unit) { + GtkTextTagTable *tag_table; + + text->_priv->layout->default_style->font_scale = + GNOME_CANVAS_ITEM(text)->canvas->pixels_per_unit; + + tag_table = gtk_text_buffer_get_tag_table(get_buffer(text)); + gtk_text_tag_table_foreach(tag_table, scale_fonts, text); + + gtk_text_layout_default_style_changed(text->_priv->layout); + } + + if (text->_priv->grow_height) { + int width, height; + + gtk_text_layout_get_size(text->_priv->layout, &width, &height); + + if (height > text->_priv->height) + text->_priv->height = height; + } + + /* We are called from the update cycle; gotta put this in an idle + loop. */ + g_idle_add(request_update, text); +} /* changed_handler */ + + +/** + * gnome_canvas_rich_text_set_buffer: + * @text: a #GnomeCanvasRichText. + * @buffer: a #GtkTextBuffer. + * + * Sets the buffer field of the @text to @buffer. + **/ +void +gnome_canvas_rich_text_set_buffer(GnomeCanvasRichText *text, + GtkTextBuffer *buffer) +{ + g_return_if_fail(GNOME_IS_CANVAS_RICH_TEXT(text)); + g_return_if_fail(buffer == NULL || GTK_IS_TEXT_BUFFER(buffer)); + + if (text->_priv->buffer == buffer) + return; + + if (text->_priv->buffer != NULL) { + g_object_unref(G_OBJECT(text->_priv->buffer)); + } + + text->_priv->buffer = buffer; + + if (buffer) { + g_object_ref(G_OBJECT(buffer)); + + if (text->_priv->layout) + gtk_text_layout_set_buffer(text->_priv->layout, buffer); + } + + gnome_canvas_item_request_update(GNOME_CANVAS_ITEM(text)); +} /* gnome_canvas_rich_text_set_buffer */ + +static GtkTextBuffer * +get_buffer(GnomeCanvasRichText *text) +{ + if (!text->_priv->buffer) { + GtkTextBuffer *b; + + b = gtk_text_buffer_new(NULL); + gnome_canvas_rich_text_set_buffer(text, b); + g_object_unref(G_OBJECT(b)); + } + + return text->_priv->buffer; +} /* get_buffer */ + + +/** + * gnome_canvas_rich_text_get_buffer: + * @text: a #GnomeCanvasRichText. + * + * Returns a #GtkTextBuffer associated with the #GnomeCanvasRichText. + * This function creates a new #GtkTextBuffer if the text buffer is NULL. + * + * Return value: the #GtkTextBuffer. + **/ +GtkTextBuffer * +gnome_canvas_rich_text_get_buffer(GnomeCanvasRichText *text) +{ + g_return_val_if_fail(GNOME_IS_CANVAS_RICH_TEXT(text), NULL); + + return get_buffer(text); +} /* gnome_canvas_rich_text_get_buffer */ + + +/** + * gnome_canvas_rich_text_get_iter_location: + * @text: a #GnomeCanvasRichText. + * @iter: a #GtkTextIter. + * @location: a #GdkRectangle containing the bounds of the character at @iter. + * + * Gets a rectangle which roughly contains the character at @iter. + **/ +void +gnome_canvas_rich_text_get_iter_location (GnomeCanvasRichText *text, + const GtkTextIter *iter, + GdkRectangle *location) +{ + g_return_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text)); + g_return_if_fail (gtk_text_iter_get_buffer (iter) == text->_priv->buffer); + + gtk_text_layout_get_iter_location (text->_priv->layout, iter, location); +} + + +/** + * gnome_canvas_rich_text_get_iter_at_location: + * @text: a #GnomeCanvasRichText. + * @iter: a #GtkTextIter. + * @x: x position, in buffer coordinates. + * @y: y position, in buffer coordinates. + * + * Retrieves the iterator at the buffer coordinates x and y. + **/ +void +gnome_canvas_rich_text_get_iter_at_location (GnomeCanvasRichText *text, + GtkTextIter *iter, + gint x, + gint y) +{ + g_return_if_fail (GNOME_IS_CANVAS_RICH_TEXT (text)); + g_return_if_fail (iter != NULL); + g_return_if_fail (text->_priv->layout != NULL); + + gtk_text_layout_get_iter_at_pixel (text->_priv->layout, + iter, + x, + y); +} + + +static void +gnome_canvas_rich_text_set_attributes_from_style(GnomeCanvasRichText *text, + GtkTextAttributes *values, + GtkStyle *style) +{ + values->appearance.bg_color = style->base[GTK_STATE_NORMAL]; + values->appearance.fg_color = style->fg[GTK_STATE_NORMAL]; + + if (values->font) + pango_font_description_free (values->font); + + values->font = pango_font_description_copy (style->font_desc); + +} /* gnome_canvas_rich_text_set_attributes_from_style */ + +static void +gnome_canvas_rich_text_ensure_layout(GnomeCanvasRichText *text) +{ + if (!text->_priv->layout) { + GtkWidget *canvas; + GtkTextAttributes *style; + PangoContext *ltr_context, *rtl_context; + + text->_priv->layout = gtk_text_layout_new(); + + gtk_text_layout_set_screen_width(text->_priv->layout, text->_priv->width); + + if (get_buffer(text)) { + gtk_text_layout_set_buffer( + text->_priv->layout, get_buffer(text)); + } + + /* Setup the cursor stuff */ + gtk_text_layout_set_cursor_visible( + text->_priv->layout, text->_priv->cursor_visible); + if (text->_priv->cursor_visible && text->_priv->cursor_blink) + gnome_canvas_rich_text_start_cursor_blink(text, FALSE); + else + gnome_canvas_rich_text_stop_cursor_blink(text); + + canvas = GTK_WIDGET(GNOME_CANVAS_ITEM(text)->canvas); + + ltr_context = gtk_widget_create_pango_context(canvas); + pango_context_set_base_dir(ltr_context, PANGO_DIRECTION_LTR); + rtl_context = gtk_widget_create_pango_context(canvas); + pango_context_set_base_dir(rtl_context, PANGO_DIRECTION_RTL); + + gtk_text_layout_set_contexts( + text->_priv->layout, ltr_context, rtl_context); + + g_object_unref(G_OBJECT(ltr_context)); + g_object_unref(G_OBJECT(rtl_context)); + + style = gtk_text_attributes_new(); + + gnome_canvas_rich_text_set_attributes_from_style( + text, style, canvas->style); + + style->pixels_above_lines = text->_priv->pixels_above_lines; + style->pixels_below_lines = text->_priv->pixels_below_lines; + style->pixels_inside_wrap = text->_priv->pixels_inside_wrap; + style->left_margin = text->_priv->left_margin; + style->right_margin = text->_priv->right_margin; + style->indent = text->_priv->indent; + style->tabs = NULL; + style->wrap_mode = text->_priv->wrap_mode; + style->justification = text->_priv->justification; + style->direction = text->_priv->direction; + style->editable = text->_priv->editable; + style->invisible = !text->_priv->visible; + + gtk_text_layout_set_default_style(text->_priv->layout, style); + + gtk_text_attributes_unref(style); + + g_signal_connect( + G_OBJECT(text->_priv->layout), "invalidated", + G_CALLBACK (invalidated_handler), text); + + g_signal_connect( + G_OBJECT(text->_priv->layout), "changed", + G_CALLBACK (changed_handler), text); + } +} /* gnome_canvas_rich_text_ensure_layout */ + +static void +gnome_canvas_rich_text_destroy_layout(GnomeCanvasRichText *text) +{ + if (text->_priv->layout) { + g_signal_handlers_disconnect_by_func( + G_OBJECT(text->_priv->layout), invalidated_handler, text); + g_signal_handlers_disconnect_by_func( + G_OBJECT(text->_priv->layout), changed_handler, text); + g_object_unref(G_OBJECT(text->_priv->layout)); + text->_priv->layout = NULL; + } +} /* gnome_canvas_rich_text_destroy_layout */ + +static void +adjust_for_anchors(GnomeCanvasRichText *text, double *ax, double *ay) +{ + double x, y; + + x = text->_priv->x; + y = text->_priv->y; + + /* Anchor text */ + /* X coordinates */ + switch (text->_priv->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + x -= text->_priv->width / 2; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + x -= text->_priv->width; + break; + default: + break; + } + + /* Y coordinates */ + switch (text->_priv->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + y -= text->_priv->height / 2; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + y -= text->_priv->height; + break; + default: + break; + } + + if (ax) + *ax = x; + if (ay) + *ay = y; +} /* adjust_for_anchors */ + +static void +get_bounds(GnomeCanvasRichText *text, double *px1, double *py1, + double *px2, double *py2) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM(text); + double x, y; + double x1, x2, y1, y2; + int cx1, cx2, cy1, cy2; + + adjust_for_anchors(text, &x, &y); + + x1 = x; + y1 = y; + x2 = x + text->_priv->width; + y2 = y + text->_priv->height; + + gnome_canvas_item_i2w(item, &x1, &y1); + gnome_canvas_item_i2w(item, &x2, &y2); + gnome_canvas_w2c(item->canvas, x1, y1, &cx1, &cy1); + gnome_canvas_w2c(item->canvas, x2, y2, &cx2, &cy2); + + *px1 = cx1; + *py1 = cy1; + *px2 = cx2; + *py2 = cy2; +} /* get_bounds */ + +static void gnome_canvas_rich_text_get_bounds(GnomeCanvasItem *item, double *px1, double *py1, + double *px2, double *py2) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + get_bounds (text, px1, py1, px2, py2); +} + +static void +gnome_canvas_rich_text_update(GnomeCanvasItem *item, double *affine, + ArtSVP *clip_path, int flags) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + double x1, y1, x2, y2; + GtkTextIter start; + + (* GNOME_CANVAS_ITEM_CLASS(parent_class)->update)( + item, affine, clip_path, flags); + + get_bounds(text, &x1, &y1, &x2, &y2); + + gtk_text_buffer_get_iter_at_offset(text->_priv->buffer, &start, 0); + if (text->_priv->layout) + gtk_text_layout_validate_yrange( + text->_priv->layout, &start, 0, y2 - y1); + + gnome_canvas_update_bbox(item, x1, y1, x2, y2); +} /* gnome_canvas_rich_text_update */ + +static double +gnome_canvas_rich_text_point(GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + double ax, ay; + double x1, x2, y1, y2; + double dx, dy; + + *actual_item = item; + + /* This is a lame cop-out. Anywhere inside of the bounding box. */ + + adjust_for_anchors(text, &ax, &ay); + + x1 = ax; + y1 = ay; + x2 = ax + text->_priv->width; + y2 = ay + text->_priv->height; + + if ((x > x1) && (y > y1) && (x < x2) && (y < y2)) + return 0.0; + + if (x < x1) + dx = x1 - x; + else if (x > x2) + dx = x - x2; + else + dx = 0.0; + + if (y < y1) + dy = y1 - y; + else if (y > y2) + dy = y - y2; + else + dy = 0.0; + + return sqrt(dx * dx + dy * dy); +} /* gnome_canvas_rich_text_point */ + +static void +gnome_canvas_rich_text_draw(GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + GnomeCanvasRichText *text = GNOME_CANVAS_RICH_TEXT(item); + double i2w[6], w2c[6], i2c[6]; + double ax, ay; + int x1, y1, x2, y2; + ArtPoint i1, i2; + ArtPoint c1, c2; + + gnome_canvas_item_i2w_affine(item, i2w); + gnome_canvas_w2c_affine(item->canvas, w2c); + art_affine_multiply(i2c, i2w, w2c); + + adjust_for_anchors(text, &ax, &ay); + + i1.x = ax; + i1.y = ay; + i2.x = ax + text->_priv->width; + i2.y = ay + text->_priv->height; + art_affine_point(&c1, &i1, i2c); + art_affine_point(&c2, &i2, i2c); + + x1 = c1.x; + y1 = c1.y; + x2 = c2.x; + y2 = c2.y; + + gtk_text_layout_set_screen_width(text->_priv->layout, x2 - x1); + + /* FIXME: should last arg be NULL? */ + gtk_text_layout_draw( + text->_priv->layout, + GTK_WIDGET(item->canvas), + drawable, + GTK_WIDGET (item->canvas)->style->text_gc[GTK_STATE_NORMAL], + x - x1, y - y1, + 0, 0, (x2 - x1) - (x - x1), (y2 - y1) - (y - y1), + NULL); +} /* gnome_canvas_rich_text_draw */ + +static void +gnome_canvas_rich_text_render(GnomeCanvasItem *item, GnomeCanvasBuf *buf) +{ + g_warning ("rich text item not implemented for anti-aliased canvas"); +} /* gnome_canvas_rich_text_render */ + +#if 0 +static GtkTextTag * +gnome_canvas_rich_text_add_tag(GnomeCanvasRichText *text, char *tag_name, + int start_offset, int end_offset, + const char *first_property_name, ...) +{ + GtkTextTag *tag; + GtkTextIter start, end; + va_list var_args; + + g_return_val_if_fail(text, NULL); + g_return_val_if_fail(start_offset >= 0, NULL); + g_return_val_if_fail(end_offset >= 0, NULL); + + if (tag_name) { + GtkTextTagTable *tag_table; + + tag_table = gtk_text_buffer_get_tag_table(get_buffer(text)); + g_return_val_if_fail(gtk_text_tag_table_lookup(tag_table, tag_name) == NULL, NULL); + } + + tag = gtk_text_buffer_create_tag( + get_buffer(text), tag_name, NULL); + + va_start(var_args, first_property_name); + g_object_set_valist(G_OBJECT(tag), first_property_name, var_args); + va_end(var_args); + + gtk_text_buffer_get_iter_at_offset( + get_buffer(text), &start, start_offset); + gtk_text_buffer_get_iter_at_offset( + get_buffer(text), &end, end_offset); + gtk_text_buffer_apply_tag(get_buffer(text), tag, &start, &end); + + return tag; +} /* gnome_canvas_rich_text_add_tag */ +#endif diff --git a/libgnomecanvas/gnome-canvas-rich-text.h b/libgnomecanvas/gnome-canvas-rich-text.h new file mode 100644 index 0000000000..4fa569e921 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-rich-text.h @@ -0,0 +1,79 @@ +/* Editable GnomeCanvas text item based on GtkTextLayout, borrowed heavily + * from GtkTextView. + * + * Copyright (c) 2000 Red Hat, Inc. + * Copyright (c) 2001 Joe Shaw + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef GNOME_CANVAS_RICH_TEXT_H +#define GNOME_CANVAS_RICH_TEXT_H + +#include <libgnomecanvas/gnome-canvas.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GNOME_TYPE_CANVAS_RICH_TEXT (gnome_canvas_rich_text_get_type ()) +#define GNOME_CANVAS_RICH_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_RICH_TEXT, GnomeCanvasRichText)) +#define GNOME_CANVAS_RICH_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_RICH_TEXT, GnomeCanvasRichTextClass)) +#define GNOME_IS_CANVAS_RICH_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_RICH_TEXT)) +#define GNOME_IS_CANVAS_RICH_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_RICH_TEXT)) +#define GNOME_CANVAS_RICH_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_RICH_TEXT, GnomeCanvasRichTextClass)) + +typedef struct _GnomeCanvasRichText GnomeCanvasRichText; +typedef struct _GnomeCanvasRichTextPrivate GnomeCanvasRichTextPrivate; +typedef struct _GnomeCanvasRichTextClass GnomeCanvasRichTextClass; + +struct _GnomeCanvasRichText { + GnomeCanvasItem item; + + GnomeCanvasRichTextPrivate *_priv; +}; + +struct _GnomeCanvasRichTextClass { + GnomeCanvasItemClass parent_class; + + void (* tag_changed)(GnomeCanvasRichText *text, + GtkTextTag *tag); +}; + +GType gnome_canvas_rich_text_get_type(void) G_GNUC_CONST; + +void gnome_canvas_rich_text_cut_clipboard(GnomeCanvasRichText *text); + +void gnome_canvas_rich_text_copy_clipboard(GnomeCanvasRichText *text); + +void gnome_canvas_rich_text_paste_clipboard(GnomeCanvasRichText *text); + +void gnome_canvas_rich_text_set_buffer(GnomeCanvasRichText *text, + GtkTextBuffer *buffer); + +GtkTextBuffer *gnome_canvas_rich_text_get_buffer(GnomeCanvasRichText *text); +void +gnome_canvas_rich_text_get_iter_location (GnomeCanvasRichText *text, + const GtkTextIter *iter, + GdkRectangle *location); +void +gnome_canvas_rich_text_get_iter_at_location (GnomeCanvasRichText *text, + GtkTextIter *iter, + gint x, + gint y); + +G_END_DECLS + +#endif /* GNOME_CANVAS_RICH_TEXT_H */ diff --git a/libgnomecanvas/gnome-canvas-shape-private.h b/libgnomecanvas/gnome-canvas-shape-private.h new file mode 100644 index 0000000000..ffdd3ce24b --- /dev/null +++ b/libgnomecanvas/gnome-canvas-shape-private.h @@ -0,0 +1,103 @@ +#ifndef GNOME_CANVAS_SHAPE_PRIVATE_H +#define GNOME_CANVAS_SHAPE_PRIVATE_H + +/* Bpath item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@acm.org> + * Lauris Kaplinski <lauris@ariman.ee> + */ + +#include <gdk/gdk.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath_dash.h> +#include <libart_lgpl/art_svp_wind.h> +#include <libgnomecanvas/gnome-canvas.h> + +#include <libgnomecanvas/gnome-canvas-path-def.h> + +G_BEGIN_DECLS + +typedef struct _GnomeCanvasShapePrivGdk GnomeCanvasShapePrivGdk; +typedef struct _GCBPDrawCtx GCBPDrawCtx; + +/* Per canvas private structure, holding necessary data for rendering + * temporary masks, which are needed for drawing multipart bpaths. + * As canvas cannot multithread, we can be sure, that masks are used + * serially, also one set of masks per canvas is sufficent to guarantee, + * that masks are created on needed X server. Masks grow as needed. + * Full structure is refcounted in Bpath implementation + */ + +struct _GCBPDrawCtx { + gint refcount; + + GnomeCanvas * canvas; + + gint width; + gint height; + + GdkBitmap * mask; + GdkBitmap * clip; + + GdkGC * clear_gc; + GdkGC * xor_gc; +}; + +/* Per Bpath private structure, holding Gdk specific data */ + +struct _GnomeCanvasShapePrivGdk { + gulong fill_pixel; /* Color for fill */ + gulong outline_pixel; /* Color for outline */ + + GdkBitmap *fill_stipple; /* Stipple for fill */ + GdkBitmap *outline_stipple; /* Stipple for outline */ + + GdkGC * fill_gc; /* GC for filling */ + GdkGC * outline_gc; /* GC for outline */ + + gint len_points; /* Size of allocated points array */ + gint num_points; /* Gdk points in canvas coords */ + GdkPoint * points; /* Ivariant: closed paths are before open ones */ + GSList * closed_paths; /* List of lengths */ + GSList * open_paths; /* List of lengths */ + + GCBPDrawCtx * ctx; /* Pointer to per-canvas drawing context */ +}; + +struct _GnomeCanvasShapePriv { + GnomeCanvasPathDef * path; /* Our bezier path representation */ + + gdouble scale; /* CTM scaling (for pen) */ + + guint fill_set : 1; /* Is fill color set? */ + guint outline_set : 1; /* Is outline color set? */ + guint width_pixels : 1; /* Is outline width specified in pixels or units? */ + + double width; /* Width of outline, in user coords */ + + guint32 fill_rgba; /* Fill color, RGBA */ + guint32 outline_rgba; /* Outline color, RGBA */ + + GdkCapStyle cap; /* Cap style for line */ + GdkJoinStyle join; /* Join style for line */ + ArtWindRule wind; /* Winding rule */ + double miterlimit; /* Miter limit */ + + ArtVpathDash dash; /* Dashing pattern */ + + ArtSVP * fill_svp; /* The SVP for the filled shape */ + ArtSVP * outline_svp; /* The SVP for the outline shape */ + + GnomeCanvasShapePrivGdk * gdk; /* Gdk specific things */ +}; + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-shape.c b/libgnomecanvas/gnome-canvas-shape.c new file mode 100644 index 0000000000..79d8124525 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-shape.c @@ -0,0 +1,1558 @@ +/* Generic bezier shape item for GnomeCanvasWidget. Most code taken + * from gnome-canvas-bpath but made into a shape item. + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent + * canvas widget. Tk is copyrighted by the Regents of the University + * of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@acm.org> + * Lauris Kaplinski <lauris@ximian.com> + * Miguel de Icaza <miguel@kernel.org> + * Cody Russell <bratsche@gnome.org> + * Rusty Conover <rconover@bangtail.net> + */ + +/* These includes are set up for standalone compile. If/when this codebase + is integrated into libgnomeui, the includes will need to change. */ + +#include <math.h> +#include <string.h> + +#include <gtk/gtk.h> +#include "gnome-canvas.h" +#include "gnome-canvas-util.h" + +#include "gnome-canvas-shape.h" +#include "gnome-canvas-shape-private.h" +#include "gnome-canvas-path-def.h" + +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_bpath.h> +#include <libart_lgpl/art_vpath_bpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_point.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_vpath_dash.h> +#include <libart_lgpl/art_svp_wind.h> +#include <libart_lgpl/art_svp_intersect.h> +#include <libart_lgpl/art_rect_svp.h> + +enum { + PROP_0, + PROP_FILL_COLOR, + PROP_FILL_COLOR_GDK, + PROP_FILL_COLOR_RGBA, + PROP_OUTLINE_COLOR, + PROP_OUTLINE_COLOR_GDK, + PROP_OUTLINE_COLOR_RGBA, + PROP_FILL_STIPPLE, + PROP_OUTLINE_STIPPLE, + PROP_WIDTH_PIXELS, + PROP_WIDTH_UNITS, + PROP_CAP_STYLE, + PROP_JOIN_STYLE, + PROP_WIND, + PROP_MITERLIMIT, + PROP_DASH +}; + +static void gnome_canvas_shape_class_init (GnomeCanvasShapeClass *class); +static void gnome_canvas_shape_init (GnomeCanvasShape *bpath); +static void gnome_canvas_shape_destroy (GtkObject *object); +static void gnome_canvas_shape_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_shape_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_shape_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static void gnome_canvas_shape_realize (GnomeCanvasItem *item); +static void gnome_canvas_shape_unrealize (GnomeCanvasItem *item); +static void gnome_canvas_shape_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); +static double gnome_canvas_shape_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item); +static void gnome_canvas_shape_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); +static void gnome_canvas_shape_bounds (GnomeCanvasItem *item, + double *x1, double *y1, double *x2, double *y2); + +static gulong get_pixel_from_rgba (GnomeCanvasItem *item, guint32 rgba_color); +static guint32 get_rgba_from_color (GdkColor * color); +static void set_gc_foreground (GdkGC *gc, gulong pixel); +static void gcbp_ensure_gdk (GnomeCanvasShape * bpath); +static void gcbp_destroy_gdk (GnomeCanvasShape * bpath); +static void set_stipple (GdkGC *gc, GdkBitmap **internal_stipple, GdkBitmap *stipple, int reconfigure); +static void gcbp_ensure_mask (GnomeCanvasShape * bpath, gint width, gint height); +static void gcbp_draw_ctx_unref (GCBPDrawCtx * ctx); + +static GnomeCanvasItemClass *parent_class; + +GType +gnome_canvas_shape_get_type (void) +{ + static GType shape_type; + + if (!shape_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasShapeClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_shape_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasShape), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_shape_init, + NULL /* value_table */ + }; + + shape_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasShape", + &object_info, 0); + } + + return shape_type; +} + +static void +gnome_canvas_shape_class_init (GnomeCanvasShapeClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + /* when this gets checked into libgnomeui, change the + GTK_TYPE_POINTER to GTK_TYPE_GNOME_CANVAS_SHAPE, and add an + entry to gnome-boxed.defs */ + + gobject_class->set_property = gnome_canvas_shape_set_property; + gobject_class->get_property = gnome_canvas_shape_get_property; + + + + g_object_class_install_property (gobject_class, + PROP_FILL_COLOR, + g_param_spec_string ("fill_color", NULL, NULL, + NULL, + (G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_FILL_COLOR_GDK, + g_param_spec_boxed ("fill_color_gdk", NULL, NULL, + GDK_TYPE_COLOR, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_FILL_COLOR_RGBA, + g_param_spec_uint ("fill_color_rgba", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_OUTLINE_COLOR, + g_param_spec_string ("outline_color", NULL, NULL, + NULL, + (G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_OUTLINE_COLOR_GDK, + g_param_spec_boxed ("outline_color_gdk", NULL, NULL, + GDK_TYPE_COLOR, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_OUTLINE_COLOR_RGBA, + g_param_spec_uint ("outline_color_rgba", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_FILL_STIPPLE, + g_param_spec_object ("fill_stipple", NULL, NULL, + GDK_TYPE_DRAWABLE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_OUTLINE_STIPPLE, + g_param_spec_object ("outline_stipple", NULL, NULL, + GDK_TYPE_DRAWABLE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_WIDTH_PIXELS, + g_param_spec_uint ("width_pixels", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_WIDTH_UNITS, + g_param_spec_double ("width_units", NULL, NULL, + 0.0, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_CAP_STYLE, + g_param_spec_enum ("cap_style", NULL, NULL, + GDK_TYPE_CAP_STYLE, + GDK_CAP_BUTT, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_JOIN_STYLE, + g_param_spec_enum ("join_style", NULL, NULL, + GDK_TYPE_JOIN_STYLE, + GDK_JOIN_MITER, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_WIND, + g_param_spec_uint ("wind", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_MITERLIMIT, + g_param_spec_double ("miterlimit", NULL, NULL, + 0.0, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property (gobject_class, + PROP_DASH, + g_param_spec_pointer ("dash", NULL, NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_shape_destroy; + + item_class->update = gnome_canvas_shape_update; + item_class->realize = gnome_canvas_shape_realize; + item_class->unrealize = gnome_canvas_shape_unrealize; + item_class->draw = gnome_canvas_shape_draw; + item_class->point = gnome_canvas_shape_point; + item_class->render = gnome_canvas_shape_render; + item_class->bounds = gnome_canvas_shape_bounds; +} + +static void +gnome_canvas_shape_init (GnomeCanvasShape *shape) +{ + shape->priv = g_new (GnomeCanvasShapePriv, 1); + + shape->priv->path = NULL; + + shape->priv->scale = 1.0; + + shape->priv->fill_set = FALSE; + shape->priv->outline_set = FALSE; + shape->priv->width_pixels = FALSE; + + shape->priv->width = 1.0; + + shape->priv->fill_rgba = 0x0000003f; + shape->priv->outline_rgba = 0x0000007f; + + shape->priv->cap = GDK_CAP_BUTT; + shape->priv->join = GDK_JOIN_MITER; + shape->priv->wind = ART_WIND_RULE_ODDEVEN; + shape->priv->miterlimit = 10.43; /* X11 default */ + + shape->priv->dash.n_dash = 0; + shape->priv->dash.dash = NULL; + + shape->priv->fill_svp = NULL; + shape->priv->outline_svp = NULL; + + shape->priv->gdk = NULL; +} + +static void +gnome_canvas_shape_destroy (GtkObject *object) +{ + GnomeCanvasShape *shape; + GnomeCanvasShapePriv *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_SHAPE (object)); + + shape = GNOME_CANVAS_SHAPE (object); + + if (shape->priv) { + priv = shape->priv; + if (priv->gdk) gcbp_destroy_gdk (shape); + + if (priv->path) gnome_canvas_path_def_unref (priv->path); + + if (priv->dash.dash) g_free (priv->dash.dash); + if (priv->fill_svp) art_svp_free (priv->fill_svp); + if (priv->outline_svp) art_svp_free (priv->outline_svp); + + g_free (shape->priv); + shape->priv = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +/** + * gnome_canvas_shape_set_path_def: + * @shape: a GnomeCanvasShape + * @def: a GnomeCanvasPathDef + * + * This function sets the the GnomeCanvasPathDef used by the + * GnomeCanvasShape. Notice, that it does not request updates, as + * it is meant to be used from item implementations, from inside + * update queue. + */ + +void +gnome_canvas_shape_set_path_def (GnomeCanvasShape *shape, GnomeCanvasPathDef *def) +{ + GnomeCanvasShapePriv *priv; + + g_return_if_fail (shape != NULL); + g_return_if_fail (GNOME_IS_CANVAS_SHAPE (shape)); + + priv = shape->priv; + + if (priv->path) { + gnome_canvas_path_def_unref (priv->path); + priv->path = NULL; + } + + if (def) { + priv->path = gnome_canvas_path_def_duplicate (def); + } +} + +static void +gnome_canvas_shape_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasShape *shape; + GnomeCanvasShapePriv *priv; + GnomeCanvasShapePrivGdk *gdk; + GdkColor color; + GdkColor *colorptr; + ArtVpathDash *dash; + + item = GNOME_CANVAS_ITEM (object); + shape = GNOME_CANVAS_SHAPE (object); + priv = shape->priv; + + if (!item->canvas->aa) { + gcbp_ensure_gdk (shape); + gdk = priv->gdk; + } else { + gdk = NULL; + } + + switch (param_id) { + case PROP_FILL_COLOR: + if (gnome_canvas_get_color (item->canvas, g_value_get_string (value), &color)) { + priv->fill_set = TRUE; + priv->fill_rgba = get_rgba_from_color (&color); + if (gdk) gdk->fill_pixel = color.pixel; + } else if (priv->fill_set) + priv->fill_set = FALSE; + else + break; + + gnome_canvas_item_request_update (item); + break; + + case PROP_FILL_COLOR_GDK: + colorptr = g_value_get_boxed (value); + if (colorptr != NULL) { + priv->fill_set = TRUE; + priv->fill_rgba = get_rgba_from_color (colorptr); + if (gdk) { + GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas)); + GdkColor tmp = *colorptr; + gdk_rgb_find_color (colormap, &tmp); + gdk->fill_pixel = tmp.pixel; + } + } else if (priv->fill_set) + priv->fill_set = FALSE; + else + break; + + gnome_canvas_item_request_update (item); + break; + + case PROP_FILL_COLOR_RGBA: + priv->fill_set = TRUE; + priv->fill_rgba = g_value_get_uint (value); + if (gdk) gdk->fill_pixel = get_pixel_from_rgba (item, priv->fill_rgba); + + gnome_canvas_item_request_update (item); + break; + + case PROP_OUTLINE_COLOR: + if (gnome_canvas_get_color (item->canvas, g_value_get_string (value), &color)) { + priv->outline_set = TRUE; + priv->outline_rgba = get_rgba_from_color (&color); + if (gdk) gdk->outline_pixel = color.pixel; + } else if (priv->outline_set) + priv->outline_set = FALSE; + else + break; + + gnome_canvas_item_request_update (item); + break; + + case PROP_OUTLINE_COLOR_GDK: + colorptr = g_value_get_boxed (value); + if (colorptr != NULL) { + priv->outline_set = TRUE; + priv->outline_rgba = get_rgba_from_color (colorptr); + if (gdk) { + GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas)); + GdkColor tmp = *colorptr; + gdk_rgb_find_color (colormap, &tmp); + gdk->outline_pixel = tmp.pixel; + } + } else if (priv->outline_set) + priv->outline_set = FALSE; + else + break; + + gnome_canvas_item_request_update (item); + break; + + case PROP_OUTLINE_COLOR_RGBA: + priv->outline_set = TRUE; + priv->outline_rgba = g_value_get_uint (value); + if (gdk) gdk->outline_pixel = get_pixel_from_rgba (item, priv->outline_rgba); + + gnome_canvas_item_request_update (item); + break; + + case PROP_FILL_STIPPLE: + if (gdk) { + set_stipple (gdk->fill_gc, &gdk->fill_stipple, (GdkBitmap*) g_value_get_object (value), FALSE); + gnome_canvas_item_request_update (item); + } + break; + + case PROP_OUTLINE_STIPPLE: + if (gdk) { + set_stipple (gdk->outline_gc, &gdk->outline_stipple, (GdkBitmap*) g_value_get_object (value), FALSE); + gnome_canvas_item_request_update (item); + } + break; + + case PROP_WIDTH_PIXELS: + priv->width = g_value_get_uint (value); + priv->width_pixels = TRUE; + + gnome_canvas_item_request_update (item); + break; + + case PROP_WIDTH_UNITS: + priv->width = fabs (g_value_get_double (value)); + priv->width_pixels = FALSE; + + gnome_canvas_item_request_update (item); + break; + + case PROP_WIND: + priv->wind = g_value_get_uint (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_CAP_STYLE: + priv->cap = g_value_get_enum (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_JOIN_STYLE: + priv->join = g_value_get_enum (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_MITERLIMIT: + priv->miterlimit = g_value_get_double (value); + gnome_canvas_item_request_update (item); + break; + + case PROP_DASH: + dash = g_value_get_pointer (value); + if (priv->dash.dash) g_free (priv->dash.dash); + priv->dash.dash = NULL; + + if (dash) { + priv->dash.offset = dash->offset; + priv->dash.n_dash = dash->n_dash; + if (dash->dash != NULL) { + priv->dash.dash = g_new (double, dash->n_dash); + memcpy (priv->dash.dash, dash->dash, dash->n_dash * sizeof (double)); + } + } + gnome_canvas_item_request_update (item); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +/* Allocates a GdkColor structure filled with the specified pixel, and + * puts it into the specified value for returning it in the get_property + * method. + */ + +static void +get_color_value (GnomeCanvasShape *shape, gulong pixel, GValue *value) +{ + GnomeCanvas *canvas = GNOME_CANVAS_ITEM (shape)->canvas; + GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas)); + GdkColor color; + + gdk_colormap_query_color (colormap, pixel, &color); + g_value_set_boxed (value, &color); +} + +/** + * gnome_canvas_shape_get_path_def: + * @shape: a GnomeCanvasShape + * + * This function returns the #GnomeCanvasPathDef that the shape + * currently uses. It adds a reference to the #GnomeCanvasPathDef and + * returns it, if there is not a #GnomeCanvasPathDef set for the shape + * it returns NULL. + * + * Returns: a #GnomeCanvasPathDef or NULL if none is set for the shape. + */ + +GnomeCanvasPathDef * +gnome_canvas_shape_get_path_def (GnomeCanvasShape *shape) +{ + GnomeCanvasShapePriv *priv; + + g_return_val_if_fail (shape != NULL, NULL); + g_return_val_if_fail (GNOME_IS_CANVAS_SHAPE (shape), NULL); + + priv = shape->priv; + + if (priv->path) { + gnome_canvas_path_def_ref (priv->path); + return priv->path; + } + + return NULL; +} + +static void +gnome_canvas_shape_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (object); + GnomeCanvasShape *shape = GNOME_CANVAS_SHAPE (object); + GnomeCanvasShapePriv *priv = shape->priv; + GnomeCanvasShapePrivGdk *gdk; + + if (!item->canvas->aa) { + gcbp_ensure_gdk (shape); + gdk = priv->gdk; + } + else { + gdk = NULL; + } + + switch (param_id) { + case PROP_FILL_COLOR_GDK: + if (gdk) { + get_color_value (shape, gdk->fill_pixel, value); + } else { + get_color_value (shape, 0, value); + } + break; + + case PROP_OUTLINE_COLOR_GDK: + if (gdk) { + get_color_value (shape, gdk->outline_pixel, value); + } else { + get_color_value (shape, 0, value); + } + break; + + case PROP_FILL_COLOR_RGBA: + g_value_set_uint (value, priv->fill_rgba); + break; + + case PROP_OUTLINE_COLOR_RGBA: + g_value_set_uint (value, priv->outline_rgba); + break; + + case PROP_FILL_STIPPLE: + if (gdk) { + g_value_set_object (value, gdk->fill_stipple); + } else { + g_value_set_object (value, NULL); + } + break; + + case PROP_OUTLINE_STIPPLE: + if (gdk) { + g_value_set_object (value, gdk->outline_stipple); + } else { + g_value_set_object (value, NULL); + } + break; + + case PROP_WIND: + g_value_set_uint (value, priv->wind); + break; + + case PROP_CAP_STYLE: + g_value_set_enum (value, priv->cap); + break; + + case PROP_JOIN_STYLE: + g_value_set_enum (value, priv->join); + break; + + case PROP_WIDTH_PIXELS: + g_value_set_uint (value, priv->width); + break; + + case PROP_WIDTH_UNITS: + g_value_set_double (value, priv->width); + break; + + case PROP_MITERLIMIT: + g_value_set_double (value, priv->miterlimit); + break; + + case PROP_DASH: + g_value_set_pointer (value, &priv->dash); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_shape_realize (GnomeCanvasItem *item) +{ + GnomeCanvasShape *shape; + + shape = GNOME_CANVAS_SHAPE (item); + + if (parent_class->realize) + (* parent_class->realize) (item); + + if (!item->canvas->aa) { + gcbp_ensure_gdk (shape); + + g_assert(item->canvas->layout.bin_window != NULL); + + shape->priv->gdk->fill_gc = gdk_gc_new (item->canvas->layout.bin_window); + shape->priv->gdk->outline_gc = gdk_gc_new (item->canvas->layout.bin_window); + } +} + +static void +gnome_canvas_shape_unrealize (GnomeCanvasItem *item) +{ + GnomeCanvasShape *shape; + + shape = GNOME_CANVAS_SHAPE (item); + + if (!item->canvas->aa) { + g_assert (shape->priv->gdk != NULL); + + g_object_unref (shape->priv->gdk->fill_gc); + shape->priv->gdk->fill_gc = NULL; + + g_object_unref (shape->priv->gdk->outline_gc); + shape->priv->gdk->outline_gc = NULL; + } + + if (parent_class->unrealize) + (* parent_class->unrealize) (item); +} + +static void +gnome_canvas_shape_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf) +{ + GnomeCanvasShape *shape; + + shape = GNOME_CANVAS_SHAPE (item); + + if (shape->priv->fill_svp != NULL) + gnome_canvas_render_svp (buf, + shape->priv->fill_svp, + shape->priv->fill_rgba); + + if (shape->priv->outline_svp != NULL) + gnome_canvas_render_svp (buf, + shape->priv->outline_svp, + shape->priv->outline_rgba); +} + +static void +gnome_canvas_shape_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, + int y, + int width, + int height) +{ + static GdkPoint * dpoints = NULL; + static gint num_dpoints = 0; + + GnomeCanvasShape * shape; + GnomeCanvasShapePriv * priv; + GnomeCanvasShapePrivGdk * gdk; + gint i, pos, len; + GSList * l; + + shape = GNOME_CANVAS_SHAPE (item); + priv = shape->priv; + + /* We have to be realized, so gdk struct should exist! */ + + gdk = shape->priv->gdk; + g_assert (gdk != NULL); + + /* Build temporary point list, translated by -x, -y */ + + if (dpoints == NULL) { + dpoints = g_new (GdkPoint, gdk->num_points); + num_dpoints = gdk->num_points; + } else if (num_dpoints < gdk->num_points) { + dpoints = g_renew (GdkPoint, dpoints, gdk->num_points); + num_dpoints = gdk->num_points; + } + + for (i = 0; i < gdk->num_points; i++) { + dpoints[i].x = gdk->points[i].x - x; + dpoints[i].y = gdk->points[i].y - y; + } + + if (priv->fill_set) { + + /* Ensure, that we have mask and it is big enough */ + + gcbp_ensure_mask (shape, width, height); + + /* Clear mask */ + + gdk_draw_rectangle (gdk->ctx->mask, + gdk->ctx->clear_gc, + TRUE, + 0, 0, + width, height); + + /* Draw subpaths, using XOR gc */ + + pos = 0; + + for (l = gdk->closed_paths; l != NULL; l = l->next) { + len = GPOINTER_TO_INT (l->data); + + gdk_draw_polygon (gdk->ctx->mask, + gdk->ctx->xor_gc, + TRUE, + &dpoints[pos], + len); + + pos += len; + } + + /* Set bitmap to clipping mask */ + + gdk_gc_set_clip_mask (gdk->fill_gc, gdk->ctx->mask); + + /* Stipple offset */ + + if (gdk->fill_stipple) gnome_canvas_set_stipple_origin (item->canvas, gdk->fill_gc); + + /* Draw clipped rect to drawable */ + + gdk_draw_rectangle (drawable, + gdk->fill_gc, + TRUE, + 0, 0, + width, height); + } + + if (priv->outline_set) { + + /* Stipple offset */ + + if (gdk->outline_stipple) gnome_canvas_set_stipple_origin (item->canvas, gdk->outline_gc); + /* Draw subpaths */ + + pos = 0; + + for (l = gdk->closed_paths; l != NULL; l = l->next) { + len = GPOINTER_TO_INT (l->data); + + gdk_draw_polygon (drawable, + gdk->outline_gc, + FALSE, + &dpoints[pos], + len); + + pos += len; + } + + for (l = gdk->open_paths; l != NULL; l = l->next) { + len = GPOINTER_TO_INT (l->data); + + gdk_draw_lines (drawable, + gdk->outline_gc, + &dpoints[pos], + len); + + pos += len; + } + } +} + +#define GDK_POINTS_BLOCK 32 + +static void +gnome_canvas_shape_ensure_gdk_points (GnomeCanvasShapePrivGdk *gdk, gint num) +{ + if (gdk->len_points < gdk->num_points + num) { + gdk->len_points = MAX (gdk->len_points + GDK_POINTS_BLOCK, gdk->len_points + num); + gdk->points = g_renew (GdkPoint, gdk->points, gdk->len_points); + } +} + +static void +gnome_canvas_shape_update_gdk (GnomeCanvasShape * shape, double * affine, ArtSVP * clip, int flags) +{ + GnomeCanvasShapePriv * priv; + GnomeCanvasShapePrivGdk * gdk; + int x1 = 0, y1 = 0, x2 = 0, y2 = 0; + gboolean bbox_set = FALSE; + gint width = 0; /* silence gcc */ + + g_assert (!((GnomeCanvasItem *) shape)->canvas->aa); + + priv = shape->priv; + gdk = priv->gdk; + g_assert (gdk != NULL); + + if (priv->outline_set) { + GdkLineStyle style; + + if (priv->width_pixels) { + width = (int) floor (priv->width + 0.5); + /* Never select 0 pixels unless the user asked for it, + * since that is the X11 zero width lines are non-specified */ + if (priv->width_pixels != 0 && width == 0) { + width = 1; + } + } else { + width = (int) floor ((priv->width * priv->scale) + 0.5); + /* Never select 0 pixels unless the user asked for it, + * since that is the X11 zero width lines are non-speciifed */ + if (priv->width != 0 && width == 0) { + width = 1; + } + } + + /* If dashed, set it in GdkGC */ + + if ((shape->priv->dash.dash != NULL) && (shape->priv->dash.n_dash > 0)) { + gint8 * dash_list; + gint i; + + dash_list = g_new (gint8, shape->priv->dash.n_dash); + + for (i = 0; i < priv->dash.n_dash; i++) { + dash_list[i] = (gint8) shape->priv->dash.dash[i]; + } + + gdk_gc_set_dashes (gdk->outline_gc, + (gint) priv->dash.offset, + dash_list, + priv->dash.n_dash); + + g_free (dash_list); + + style = GDK_LINE_ON_OFF_DASH; + } else { + style = GDK_LINE_SOLID; + } + + /* Set line width, cap, join */ + if(gdk->outline_gc) { + + gdk_gc_set_line_attributes (gdk->outline_gc, + width, + style, + priv->cap, + priv->join); + + /* Colors and stipples */ + set_gc_foreground (gdk->outline_gc, gdk->outline_pixel); + set_stipple (gdk->outline_gc, &gdk->outline_stipple, gdk->outline_stipple, TRUE); + } + } + + if (priv->fill_set) { + + /* Colors and stipples */ + if(gdk->fill_gc) { + set_gc_foreground (gdk->fill_gc, gdk->fill_pixel); + set_stipple (gdk->fill_gc, &gdk->fill_stipple, gdk->fill_stipple, TRUE); + } + } + + /* Now the crazy part */ + + /* Free existing GdkPoint array */ + + if (gdk->points) { + g_free (gdk->points); + gdk->points = NULL; + gdk->len_points = 0; + gdk->num_points = 0; + } + + /* Free subpath lists */ + + while (gdk->closed_paths) gdk->closed_paths = g_slist_remove (gdk->closed_paths, gdk->closed_paths->data); + while (gdk->open_paths) gdk->open_paths = g_slist_remove (gdk->open_paths, gdk->open_paths->data); + + /* Calcualte new GdkPoints array and subpath lists */ + + if (priv->path) { + GnomeCanvasPathDef * apath, * cpath, * opath; + ArtBpath * abpath; + GSList * clist, * olist; + gint pos; + +#if 0 + /* Allocate array */ + gdk->num_points = gnome_canvas_path_def_length (priv->path) * 1000 - 1; + gdk->points = g_new (GdkPoint, gdk->num_points); + g_print ("Points %d\n", gdk->num_points); + /* Transform path */ +#endif + + abpath = art_bpath_affine_transform (gnome_canvas_path_def_bpath (priv->path), affine); + apath = gnome_canvas_path_def_new_from_bpath (abpath); + + /* Split path into open and closed parts */ + + cpath = gnome_canvas_path_def_closed_parts (apath); + opath = gnome_canvas_path_def_open_parts (apath); + gnome_canvas_path_def_unref (apath); + + /* Split partial paths into subpaths */ + + clist = gnome_canvas_path_def_split (cpath); + gnome_canvas_path_def_unref (cpath); + olist = gnome_canvas_path_def_split (opath); + gnome_canvas_path_def_unref (opath); + + pos = 0; + + /* Fill GdkPoints and add subpaths to list: closed subpaths */ + + while (clist) { + GnomeCanvasPathDef * path; + ArtBpath * bpath; + ArtVpath * vpath; + gint len, i; + + path = (GnomeCanvasPathDef *) clist->data; + bpath = gnome_canvas_path_def_bpath (path); + vpath = art_bez_path_to_vec (bpath, 0.1); + for (len = 0; vpath[len].code != ART_END; len++) ; + + gnome_canvas_shape_ensure_gdk_points (gdk, len); + for (i = 0; i < len; i++) { + gdk->points[pos + i].x = (gint) floor (vpath[i].x + 0.5); + gdk->points[pos + i].y = (gint) floor (vpath[i].y + 0.5); + + if (bbox_set) { + x1 = MIN (x1, gdk->points[pos + i].x); + x2 = MAX (x2, gdk->points[pos + i].x); + y1 = MIN (y1, gdk->points[pos + i].y); + y2 = MAX (y2, gdk->points[pos + i].y); + } else { + bbox_set = TRUE; + x1 = x2 = gdk->points[pos + i].x; + y1 = y2 = gdk->points[pos + i].y; + } + } + gdk->num_points += len; + + art_free (vpath); + + if (len > 0) { + pos += len; + gdk->closed_paths = g_slist_append (gdk->closed_paths, GINT_TO_POINTER (len)); + } + + gnome_canvas_path_def_unref (path); + clist = g_slist_remove (clist, clist->data); + } + + /* Fill GdkPoints and add subpaths to list: open subpaths */ + + while (olist) { + GnomeCanvasPathDef * path; + ArtBpath * bpath; + ArtVpath * vpath; + gint len, i; + + path = (GnomeCanvasPathDef *) olist->data; + bpath = gnome_canvas_path_def_bpath (path); + vpath = art_bez_path_to_vec (bpath, 0.1); + for (len = 0; vpath[len].code != ART_END; len++) ; + + gnome_canvas_shape_ensure_gdk_points (gdk, len); + for (i = 0; i < len; i++) { + gdk->points[pos + i].x = (gint) floor (vpath[i].x + 0.5); + gdk->points[pos + i].y = (gint) floor (vpath[i].y + 0.5); + + if (bbox_set) { + x1 = MIN (x1, gdk->points[pos + i].x); + x2 = MAX (x2, gdk->points[pos + i].x); + y1 = MIN (y1, gdk->points[pos + i].y); + y2 = MAX (y2, gdk->points[pos + i].y); + } else { + bbox_set = TRUE; + x1 = x2 = gdk->points[pos + i].x; + y1 = y2 = gdk->points[pos + i].y; + } + } + gdk->num_points += len; + + art_free (vpath); + + if (len > 0) { + pos += len; + gdk->open_paths = g_slist_append (gdk->open_paths, GINT_TO_POINTER (len)); + } + + gnome_canvas_path_def_unref (path); + olist = g_slist_remove (olist, olist->data); + } + + } + + if (bbox_set) { + if (priv->outline_set) { + int stroke_border = (priv->join == GDK_JOIN_MITER) + ? ceil (10.43*width/2) /* 10.43 is the miter limit for X11 */ + : ceil (width/2); + x1 -= stroke_border; + x2 += stroke_border; + y1 -= stroke_border; + y2 += stroke_border; + } + + gnome_canvas_update_bbox (GNOME_CANVAS_ITEM (shape), + x1, y1, + x2 + 1, y2 + 1); + } + +} + +static void +gnome_canvas_shape_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasShape * shape; + GnomeCanvasShapePriv * priv; + ArtSVP * svp; + + shape = GNOME_CANVAS_SHAPE (item); + + priv = shape->priv; + + /* Common part */ + if (parent_class->update) { + (* parent_class->update) (item, affine, clip_path, flags); + } + + /* Outline width */ + shape->priv->scale = art_affine_expansion (affine); + + /* Reset bbox */ + if (item->canvas->aa) { + gnome_canvas_item_reset_bounds (item); + } + + /* Clipped fill SVP */ + + if ((priv->fill_set) && (priv->path) && (gnome_canvas_path_def_any_closed (priv->path))) { + GnomeCanvasPathDef * cpath; + ArtSvpWriter *swr; + ArtVpath *vpath; + ArtBpath *abp; + ArtSVP *svp2; + + /* Get closed part of path */ + + cpath = gnome_canvas_path_def_closed_parts (shape->priv->path); + abp = art_bpath_affine_transform (gnome_canvas_path_def_bpath (cpath), affine); + gnome_canvas_path_def_unref (cpath); + + /* Render, until SVP */ + + vpath = art_bez_path_to_vec (abp, 0.1); + art_free (abp); + + svp = art_svp_from_vpath (vpath); + art_free (vpath); + + swr = art_svp_writer_rewind_new (shape->priv->wind); + art_svp_intersector (svp, swr); + + svp2 = art_svp_writer_rewind_reap (swr); + art_svp_free (svp); + + if (item->canvas->aa) { + /* Update clipped path */ + gnome_canvas_item_update_svp_clip (item, + &shape->priv->fill_svp, + svp2, + clip_path); + } else { + if (priv->fill_svp) { + art_svp_free (priv->fill_svp); + priv->fill_svp = NULL; + } + /* No clipping */ + shape->priv->fill_svp = svp2; + } + } + + if (priv->outline_set && priv->path && !gnome_canvas_path_def_is_empty (priv->path)) { + gdouble width; + ArtBpath * abp; + ArtVpath * vpath; + + /* Set linewidth */ + + if (priv->width_pixels) { + width = priv->width; + } else { + width = priv->width * priv->scale; + } + + if (width < 0.5) width = 0.5; + + /* Render full path until vpath */ + + abp = art_bpath_affine_transform (gnome_canvas_path_def_bpath (priv->path), affine); + + vpath = art_bez_path_to_vec (abp, 0.1); + art_free (abp); + + /* If dashed, apply dash */ + + if (priv->dash.dash != NULL) + { + ArtVpath *old = vpath; + + vpath = art_vpath_dash (old, &priv->dash); + art_free (old); + } + + /* Stroke vpath to SVP */ + + svp = art_svp_vpath_stroke (vpath, + gnome_canvas_join_gdk_to_art (priv->join), + gnome_canvas_cap_gdk_to_art (priv->cap), + width, + priv->miterlimit, + 0.25); + art_free (vpath); + + if (item->canvas->aa) { + /* Update clipped */ + gnome_canvas_item_update_svp_clip (item, &priv->outline_svp, svp, clip_path); + } else { + if (priv->outline_svp) { + art_svp_free (priv->outline_svp); + priv->outline_svp = NULL; + } + /* No clipping (yet) */ + shape->priv->outline_svp = svp; + } + } + + /* Gdk requires additional handling */ + + if (!item->canvas->aa) { + gnome_canvas_shape_update_gdk (shape, affine, clip_path, flags); + } +} + +static double +gnome_canvas_shape_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item) +{ + GnomeCanvasShape *shape; + double dist; + int wind; + +#if 0 + /* fixme: This is just for debugging, canvas should ensure that */ + /* fixme: IF YOU ARE SURE THAT IT IS CORRECT BEHAVIOUR, you can remove warning */ + /* fixme: and make it to return silently */ + g_return_val_if_fail (!item->canvas->need_update, 1e18); +#endif + + shape = GNOME_CANVAS_SHAPE (item); + + /* todo: update? */ + if (shape->priv->fill_set && shape->priv->fill_svp) { + wind = art_svp_point_wind (shape->priv->fill_svp, cx, cy); + if ((shape->priv->wind == ART_WIND_RULE_NONZERO) && (wind != 0)) { + *actual_item = item; + return 0.0; + } + if ((shape->priv->wind == ART_WIND_RULE_ODDEVEN) && ((wind & 0x1) != 0)) { + *actual_item = item; + return 0.0; + } + } + + if (shape->priv->outline_set && shape->priv->outline_svp) { + wind = art_svp_point_wind (shape->priv->outline_svp, cx, cy); + if (wind) { + *actual_item = item; + return 0.0; + } + } + + if (shape->priv->outline_set && shape->priv->outline_svp) { + dist = art_svp_point_dist (shape->priv->outline_svp, cx, cy); + } else if (shape->priv->fill_set && shape->priv->outline_svp) { + dist = art_svp_point_dist (shape->priv->fill_svp, cx, cy); + } else { + return 1e12; + } + + *actual_item = item; + + return dist; +} + +/* Helpers */ + +/* Get 32bit rgba color from GdkColor */ + +static guint32 +get_rgba_from_color (GdkColor * color) +{ + return ((color->red & 0xff00) << 16) | ((color->green & 0xff00) << 8) | (color->blue & 0xff00) | 0xff; +} + +/* Get Gdk pixel value from 32bit rgba color */ + +static gulong +get_pixel_from_rgba (GnomeCanvasItem *item, guint32 rgba_color) +{ + return gnome_canvas_get_color_pixel (item->canvas, rgba_color); +} + +/* Convenience function to set a GC's foreground color to the specified pixel value */ + +static void +set_gc_foreground (GdkGC *gc, gulong pixel) +{ + GdkColor c; + + g_assert (gc != NULL); + + c.pixel = pixel; + + gdk_gc_set_foreground (gc, &c); +} + +/* Sets the stipple pattern for the specified gc */ + +static void +set_stipple (GdkGC *gc, GdkBitmap **internal_stipple, GdkBitmap *stipple, int reconfigure) +{ + if (*internal_stipple && !reconfigure) + g_object_unref (*internal_stipple); + + *internal_stipple = stipple; + if (stipple && !reconfigure) + g_object_ref (stipple); + + if (gc) { + if (stipple) { + gdk_gc_set_stipple (gc, stipple); + gdk_gc_set_fill (gc, GDK_STIPPLED); + } else + gdk_gc_set_fill (gc, GDK_SOLID); + } +} + +/* Creates private Gdk struct, if not present */ +/* We cannot do it during ::init, as we have to know canvas */ + +static void +gcbp_ensure_gdk (GnomeCanvasShape * shape) +{ + g_assert (!((GnomeCanvasItem *) shape)->canvas->aa); + + if (!shape->priv->gdk) { + GnomeCanvasShapePrivGdk * gdk; + + gdk = g_new (GnomeCanvasShapePrivGdk, 1); + + gdk->fill_pixel = get_pixel_from_rgba ((GnomeCanvasItem *) shape, shape->priv->fill_rgba); + gdk->outline_pixel = get_pixel_from_rgba ((GnomeCanvasItem *) shape, shape->priv->outline_rgba); + + gdk->fill_stipple = NULL; + gdk->outline_stipple = NULL; + + gdk->fill_gc = NULL; + gdk->outline_gc = NULL; + + gdk->len_points = 0; + gdk->num_points = 0; + gdk->points = NULL; + gdk->closed_paths = NULL; + gdk->open_paths = NULL; + + gdk->ctx = NULL; + + shape->priv->gdk = gdk; + } +} + +/* Destroy private Gdk struct */ +/* It is here, to make ::destroy implementation shorter :) */ + +static void +gcbp_destroy_gdk (GnomeCanvasShape * shape) +{ + GnomeCanvasShapePrivGdk * gdk; + + g_assert (!((GnomeCanvasItem *)shape)->canvas->aa); + + gdk = shape->priv->gdk; + + if (gdk) { + g_assert (!gdk->fill_gc); + g_assert (!gdk->outline_gc); + + if (gdk->fill_stipple) + g_object_unref (gdk->fill_stipple); + + if (gdk->outline_stipple) + g_object_unref (gdk->outline_stipple); + + if (gdk->points) + g_free (gdk->points); + + while (gdk->closed_paths) + gdk->closed_paths = g_slist_remove (gdk->closed_paths, gdk->closed_paths->data); + while (gdk->open_paths) + gdk->open_paths = g_slist_remove (gdk->open_paths, gdk->open_paths->data); + + if (gdk->ctx) + gcbp_draw_ctx_unref (gdk->ctx); + + g_free (gdk); + + shape->priv->gdk = NULL; + } +} + +/* + * Ensure, that per-canvas Ctx struct is present and bitmaps are + * big enough, to mask full redraw area. Ctx is refcounted and + * defined as "BpathDrawCtx" data member on parent canvas + */ + +static void +gcbp_ensure_mask (GnomeCanvasShape * shape, gint width, gint height) +{ + GnomeCanvasShapePrivGdk * gdk; + GCBPDrawCtx * ctx; + + gdk = shape->priv->gdk; + g_assert (gdk != NULL); + ctx = gdk->ctx; + + if (!ctx) { + /* Ctx is not yet defined for us */ + + GnomeCanvas * canvas; + + canvas = GNOME_CANVAS_ITEM (shape)->canvas; + + ctx = g_object_get_data (G_OBJECT (canvas), "BpathDrawCtx"); + + if (!ctx) { + /* Ctx is not defined for parent canvas yet */ + + ctx = g_new (GCBPDrawCtx, 1); + + ctx->refcount = 1; + ctx->canvas = canvas; + ctx->width = 0; + ctx->height = 0; + + ctx->mask = NULL; + ctx->clip = NULL; + + ctx->clear_gc = NULL; + ctx->xor_gc = NULL; + + g_object_set_data (G_OBJECT (canvas), "BpathDrawCtx", ctx); + + } else { + ctx->refcount++; + } + + gdk->ctx = ctx; + + } + + /* Now we are sure, that ctx is present and properly refcounted */ + + if ((width > ctx->width) || (height > ctx->height)) { + /* Ctx is too small */ + + GdkWindow * window; + + window = ((GtkWidget *) (((GnomeCanvasItem *) shape)->canvas))->window; + + if (ctx->clear_gc) g_object_unref (ctx->clear_gc); + if (ctx->xor_gc) g_object_unref (ctx->xor_gc); + if (ctx->mask) g_object_unref (ctx->mask); + if (ctx->clip) g_object_unref (ctx->clip); + + ctx->mask = gdk_pixmap_new (window, width, height, 1); + ctx->clip = NULL; + + ctx->clear_gc = gdk_gc_new (ctx->mask); + gdk_gc_set_function (ctx->clear_gc, GDK_CLEAR); + + ctx->xor_gc = gdk_gc_new (ctx->mask); + gdk_gc_set_function (ctx->xor_gc, GDK_INVERT); + } +} + +/* It is cleaner to have it here, not in parent function */ + +static void +gcbp_draw_ctx_unref (GCBPDrawCtx * ctx) +{ + if (--ctx->refcount < 1) { + if (ctx->clear_gc) + g_object_unref (ctx->clear_gc); + if (ctx->xor_gc) + g_object_unref (ctx->xor_gc); + + if (ctx->mask) + g_object_unref (ctx->mask); + if (ctx->clip) + g_object_unref (ctx->clip); + + g_object_set_data (G_OBJECT (ctx->canvas), "BpathDrawCtx", NULL); + g_free (ctx); + } +} + +static void +gnome_canvas_shape_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasShape * shape; + GnomeCanvasShapePriv * priv; + ArtDRect bbox; + ArtSVP * svp; + + shape = GNOME_CANVAS_SHAPE (item); + + priv = shape->priv; + + bbox.x0 = *x1; + bbox.y0 = *y1; + bbox.x1 = *x2; + bbox.y1 = *y2; + + if (priv->outline_set && priv->path && !gnome_canvas_path_def_is_empty (priv->path)) { + gdouble width; + ArtVpath * vpath; + + /* Set linewidth */ + + if (priv->width_pixels) { + width = priv->width; + } else { + width = priv->width * priv->scale; + } + + if (width < 0.5) width = 0.5; + + /* Render full path until vpath */ + + vpath = art_bez_path_to_vec (gnome_canvas_path_def_bpath (priv->path), 0.1); + + /* If dashed, apply dash */ + + if (priv->dash.dash != NULL) + { + ArtVpath *old = vpath; + + vpath = art_vpath_dash (old, &priv->dash); + art_free (old); + } + + /* Stroke vpath to SVP */ + + svp = art_svp_vpath_stroke (vpath, + gnome_canvas_join_gdk_to_art (priv->join), + gnome_canvas_cap_gdk_to_art (priv->cap), + width, + priv->miterlimit, + 0.25); + art_free (vpath); + art_drect_svp (&bbox, svp); + art_svp_free (svp); + } else if ((priv->fill_set) && (priv->path) && (gnome_canvas_path_def_any_closed (priv->path))) { + GnomeCanvasPathDef *cpath; + ArtSvpWriter *swr; + ArtVpath *vpath; + ArtSVP *svp2; + + /* Get closed part of path */ + cpath = gnome_canvas_path_def_closed_parts (shape->priv->path); + /* Render, until SVP */ + vpath = art_bez_path_to_vec (gnome_canvas_path_def_bpath (cpath), 0.1); + gnome_canvas_path_def_unref (cpath); + + svp = art_svp_from_vpath (vpath); + art_free (vpath); + + swr = art_svp_writer_rewind_new (shape->priv->wind); + art_svp_intersector (svp, swr); + + svp2 = art_svp_writer_rewind_reap (swr); + art_svp_free (svp); + + art_drect_svp (&bbox, svp2); + art_svp_free (svp2); + } + + *x1 = bbox.x0; + *y1 = bbox.y0; + *x2 = bbox.x1; + *y2 = bbox.y1; +} diff --git a/libgnomecanvas/gnome-canvas-shape.h b/libgnomecanvas/gnome-canvas-shape.h new file mode 100644 index 0000000000..b91578ba6b --- /dev/null +++ b/libgnomecanvas/gnome-canvas-shape.h @@ -0,0 +1,81 @@ +/* Generic bezier shape item for GnomeCanvas + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Copyright (C) 1998,1999 The Free Software Foundation + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@acm.org> + * Lauris Kaplinski <lauris@ximian.com> + * Rusty Conover <rconover@bangtail.net> + */ + +#ifndef GNOME_CANVAS_SHAPE_H +#define GNOME_CANVAS_SHAPE_H + +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-path-def.h> + +G_BEGIN_DECLS + + +/* Shape item for the canvas. + * + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * fill_color string W X color specification for fill color, + * or NULL pointer for no color (transparent). + * fill_color_gdk GdkColor* RW Allocated GdkColor for fill. + * outline_color string W X color specification for outline color, + * or NULL pointer for no color (transparent). + * outline_color_gdk GdkColor* RW Allocated GdkColor for outline. + * fill_stipple GdkBitmap* RW Stipple pattern for fill + * outline_stipple GdkBitmap* RW Stipple pattern for outline + * width_pixels uint RW Width of the outline in pixels. The outline will + * not be scaled when the canvas zoom factor is changed. + * width_units double RW Width of the outline in canvas units. The outline + * will be scaled when the canvas zoom factor is changed. + * cap_style GdkCapStyle RW Cap ("endpoint") style for the bpath. + * join_style GdkJoinStyle RW Join ("vertex") style for the bpath. + * wind ArtWindRule RW Winding rule for the bpath. + * dash ArtVpathDash RW Dashing pattern + * miterlimit double RW Minimum angle between segments, where miter join + * rule is applied. + */ + +#define GNOME_TYPE_CANVAS_SHAPE (gnome_canvas_shape_get_type ()) +#define GNOME_CANVAS_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_SHAPE, GnomeCanvasShape)) +#define GNOME_CANVAS_SHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_SHAPE, GnomeCanvasShapeClass)) +#define GNOME_IS_CANVAS_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_SHAPE)) +#define GNOME_IS_CANVAS_SHAPE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_SHAPE)) + + +typedef struct _GnomeCanvasShape GnomeCanvasShape; +typedef struct _GnomeCanvasShapePriv GnomeCanvasShapePriv; +typedef struct _GnomeCanvasShapeClass GnomeCanvasShapeClass; + +struct _GnomeCanvasShape { + GnomeCanvasItem item; + + GnomeCanvasShapePriv *priv; /* Private data */ +}; + +struct _GnomeCanvasShapeClass { + GnomeCanvasItemClass parent_class; +}; + + +/* WARNING! These are not usable from modifying shapes from user programs */ +/* These are meant, to set master shape from subclass ::update method */ +void gnome_canvas_shape_set_path_def (GnomeCanvasShape *shape, GnomeCanvasPathDef *def); +GnomeCanvasPathDef *gnome_canvas_shape_get_path_def (GnomeCanvasShape *shape); + +/* Standard Gtk function */ +GType gnome_canvas_shape_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-text.c b/libgnomecanvas/gnome-canvas-text.c new file mode 100644 index 0000000000..81cc357e41 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-text.c @@ -0,0 +1,1746 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * $Id$ + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Text item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas + * widget. Tk is copyrighted by the Regents of the University of California, + * Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + * Port to Pango co-done by Gergõ Érdi <cactus@cactus.rulez.org> + */ + +#include <config.h> +#include <math.h> +#include <string.h> +#include "gnome-canvas-text.h" +#include <pango/pangoft2.h> + +#include <libart_lgpl/art_affine.h> +#include <libart_lgpl/art_rgb_a_affine.h> +#include <libart_lgpl/art_rgb.h> +#include <libart_lgpl/art_rgb_bitmap_affine.h> +#include "gnome-canvas-util.h" +#include "gnome-canvas-i18n.h" + + + +/* Object argument IDs */ +enum { + PROP_0, + + /* Text contents */ + PROP_TEXT, + PROP_MARKUP, + + /* Position */ + PROP_X, + PROP_Y, + + /* Font */ + PROP_FONT, + PROP_FONT_DESC, + PROP_FAMILY, PROP_FAMILY_SET, + + /* Style */ + PROP_ATTRIBUTES, + PROP_STYLE, PROP_STYLE_SET, + PROP_VARIANT, PROP_VARIANT_SET, + PROP_WEIGHT, PROP_WEIGHT_SET, + PROP_STRETCH, PROP_STRETCH_SET, + PROP_SIZE, PROP_SIZE_SET, + PROP_SIZE_POINTS, + PROP_STRIKETHROUGH, PROP_STRIKETHROUGH_SET, + PROP_UNDERLINE, PROP_UNDERLINE_SET, + PROP_RISE, PROP_RISE_SET, + PROP_SCALE, PROP_SCALE_SET, + + /* Clipping */ + PROP_ANCHOR, + PROP_JUSTIFICATION, + PROP_CLIP_WIDTH, + PROP_CLIP_HEIGHT, + PROP_CLIP, + PROP_X_OFFSET, + PROP_Y_OFFSET, + + /* Coloring */ + PROP_FILL_COLOR, + PROP_FILL_COLOR_GDK, + PROP_FILL_COLOR_RGBA, + PROP_FILL_STIPPLE, + + /* Rendered size accessors */ + PROP_TEXT_WIDTH, + PROP_TEXT_HEIGHT +}; + +struct _GnomeCanvasTextPrivate { + guint render_dirty : 1; + FT_Bitmap bitmap; +}; + + +static void gnome_canvas_text_class_init (GnomeCanvasTextClass *class); +static void gnome_canvas_text_init (GnomeCanvasText *text); +static void gnome_canvas_text_destroy (GtkObject *object); +static void gnome_canvas_text_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_text_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_text_update (GnomeCanvasItem *item, double *affine, + ArtSVP *clip_path, int flags); +static void gnome_canvas_text_realize (GnomeCanvasItem *item); +static void gnome_canvas_text_unrealize (GnomeCanvasItem *item); +static void gnome_canvas_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); +static double gnome_canvas_text_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item); +static void gnome_canvas_text_bounds (GnomeCanvasItem *item, + double *x1, double *y1, double *x2, double *y2); +static void gnome_canvas_text_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); + +static void gnome_canvas_text_set_markup (GnomeCanvasText *textitem, + const gchar *markup); + +static void gnome_canvas_text_set_font_desc (GnomeCanvasText *textitem, + PangoFontDescription *font_desc); + +static void gnome_canvas_text_apply_font_desc (GnomeCanvasText *textitem); +static void gnome_canvas_text_apply_attributes (GnomeCanvasText *textitem); + +static void add_attr (PangoAttrList *attr_list, + PangoAttribute *attr); + +static GnomeCanvasItemClass *parent_class; + + + +/** + * gnome_canvas_text_get_type: + * @void: + * + * Registers the &GnomeCanvasText class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GnomeCanvasText class. + **/ +GType +gnome_canvas_text_get_type (void) +{ + static GType text_type; + + if (!text_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasTextClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_text_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasText), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_text_init, + NULL /* value_table */ + }; + + text_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasText", + &object_info, 0); + } + + return text_type; +} + +/* Class initialization function for the text item */ +static void +gnome_canvas_text_class_init (GnomeCanvasTextClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_text_set_property; + gobject_class->get_property = gnome_canvas_text_get_property; + + /* Text */ + g_object_class_install_property + (gobject_class, + PROP_TEXT, + g_param_spec_string ("text", + _("Text"), + _("Text to render"), + NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, + PROP_MARKUP, + g_param_spec_string ("markup", + _("Markup"), + _("Marked up text to render"), + NULL, + (G_PARAM_WRITABLE))); + + /* Position */ + g_object_class_install_property + (gobject_class, + PROP_X, + g_param_spec_double ("x", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, + PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + + /* Font */ + g_object_class_install_property + (gobject_class, + PROP_FONT, + g_param_spec_string ("font", + _("Font"), + _("Font description as a string"), + NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, + PROP_FONT_DESC, + g_param_spec_boxed ("font_desc", + _("Font description"), + _("Font description as a PangoFontDescription struct"), + PANGO_TYPE_FONT_DESCRIPTION, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, + PROP_FAMILY, + g_param_spec_string ("family", + _("Font family"), + _("Name of the font family, e.g. Sans, Helvetica, Times, Monospace"), + NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + /* Style */ + g_object_class_install_property + (gobject_class, + PROP_ATTRIBUTES, + g_param_spec_boxed ("attributes", NULL, NULL, + PANGO_TYPE_ATTR_LIST, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + g_object_class_install_property + (gobject_class, + PROP_STYLE, + g_param_spec_enum ("style", + _("Font style"), + _("Font style"), + PANGO_TYPE_STYLE, + PANGO_STYLE_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_VARIANT, + g_param_spec_enum ("variant", + _("Font variant"), + _("Font variant"), + PANGO_TYPE_VARIANT, + PANGO_VARIANT_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_WEIGHT, + g_param_spec_int ("weight", + _("Font weight"), + _("Font weight"), + 0, + G_MAXINT, + PANGO_WEIGHT_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + + g_object_class_install_property + (gobject_class, + PROP_STRETCH, + g_param_spec_enum ("stretch", + _("Font stretch"), + _("Font stretch"), + PANGO_TYPE_STRETCH, + PANGO_STRETCH_NORMAL, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_SIZE, + g_param_spec_int ("size", + _("Font size"), + _("Font size (as a multiple of PANGO_SCALE, eg. 12*PANGO_SCALE for a 12pt font size)"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_SIZE_POINTS, + g_param_spec_double ("size_points", + _("Font points"), + _("Font size in points (eg. 12 for a 12pt font size)"), + 0.0, + G_MAXDOUBLE, + 0.0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_RISE, + g_param_spec_int ("rise", + _("Rise"), + _("Offset of text above the baseline (below the baseline if rise is negative)"), + -G_MAXINT, + G_MAXINT, + 0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_STRIKETHROUGH, + g_param_spec_boolean ("strikethrough", + _("Strikethrough"), + _("Whether to strike through the text"), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_UNDERLINE, + g_param_spec_enum ("underline", + _("Underline"), + _("Style of underline for this text"), + PANGO_TYPE_UNDERLINE, + PANGO_UNDERLINE_NONE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_SCALE, + g_param_spec_double ("scale", + _("Scale"), + _("Size of font, relative to default size"), + 0.0, + G_MAXDOUBLE, + 1.0, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + + g_object_class_install_property + (gobject_class, + PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_CENTER, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_JUSTIFICATION, + g_param_spec_enum ("justification", NULL, NULL, + GTK_TYPE_JUSTIFICATION, + GTK_JUSTIFY_LEFT, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_CLIP_WIDTH, + g_param_spec_double ("clip_width", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_CLIP_HEIGHT, + g_param_spec_double ("clip_height", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_CLIP, + g_param_spec_boolean ("clip", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_X_OFFSET, + g_param_spec_double ("x_offset", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y_OFFSET, + g_param_spec_double ("y_offset", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR, + g_param_spec_string ("fill_color", + _("Color"), + _("Text color, as string"), + NULL, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR_GDK, + g_param_spec_boxed ("fill_color_gdk", + _("Color"), + _("Text color, as a GdkColor"), + GDK_TYPE_COLOR, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_COLOR_RGBA, + g_param_spec_uint ("fill_color_rgba", + _("Color"), + _("Text color, as an R/G/B/A combined integer"), + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_FILL_STIPPLE, + g_param_spec_object ("fill_stipple", NULL, NULL, + GDK_TYPE_DRAWABLE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_TEXT_WIDTH, + g_param_spec_double ("text_width", + _("Text width"), + _("Width of the rendered text"), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + g_object_class_install_property + (gobject_class, + PROP_TEXT_HEIGHT, + g_param_spec_double ("text_height", + _("Text height"), + _("Height of the rendered text"), + 0.0, G_MAXDOUBLE, 0.0, + G_PARAM_READABLE)); + + /* Style props are set (explicitly applied) or not */ +#define ADD_SET_PROP(propname, propval, nick, blurb) g_object_class_install_property (gobject_class, propval, g_param_spec_boolean (propname, nick, blurb, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)) + + ADD_SET_PROP ("family_set", PROP_FAMILY_SET, + _("Font family set"), + _("Whether this tag affects the font family")); + + ADD_SET_PROP ("style_set", PROP_STYLE_SET, + _("Font style set"), + _("Whether this tag affects the font style")); + + ADD_SET_PROP ("variant_set", PROP_VARIANT_SET, + _("Font variant set"), + _("Whether this tag affects the font variant")); + + ADD_SET_PROP ("weight_set", PROP_WEIGHT_SET, + _("Font weight set"), + _("Whether this tag affects the font weight")); + + ADD_SET_PROP ("stretch_set", PROP_STRETCH_SET, + _("Font stretch set"), + _("Whether this tag affects the font stretch")); + + ADD_SET_PROP ("size_set", PROP_SIZE_SET, + _("Font size set"), + _("Whether this tag affects the font size")); + + ADD_SET_PROP ("rise_set", PROP_RISE_SET, + _("Rise set"), + _("Whether this tag affects the rise")); + + ADD_SET_PROP ("strikethrough_set", PROP_STRIKETHROUGH_SET, + _("Strikethrough set"), + _("Whether this tag affects strikethrough")); + + ADD_SET_PROP ("underline_set", PROP_UNDERLINE_SET, + _("Underline set"), + _("Whether this tag affects underlining")); + + ADD_SET_PROP ("scale_set", PROP_SCALE_SET, + _("Scale set"), + _("Whether this tag affects font scaling")); +#undef ADD_SET_PROP + + object_class->destroy = gnome_canvas_text_destroy; + + item_class->update = gnome_canvas_text_update; + item_class->realize = gnome_canvas_text_realize; + item_class->unrealize = gnome_canvas_text_unrealize; + item_class->draw = gnome_canvas_text_draw; + item_class->point = gnome_canvas_text_point; + item_class->bounds = gnome_canvas_text_bounds; + item_class->render = gnome_canvas_text_render; +} + +/* Object initialization function for the text item */ +static void +gnome_canvas_text_init (GnomeCanvasText *text) +{ + text->x = 0.0; + text->y = 0.0; + text->anchor = GTK_ANCHOR_CENTER; + text->justification = GTK_JUSTIFY_LEFT; + text->clip_width = 0.0; + text->clip_height = 0.0; + text->xofs = 0.0; + text->yofs = 0.0; + text->layout = NULL; + + text->font_desc = NULL; + + text->underline = PANGO_UNDERLINE_NONE; + text->strikethrough = FALSE; + text->rise = 0; + + text->underline_set = FALSE; + text->strike_set = FALSE; + text->rise_set = FALSE; + + text->priv = g_new (GnomeCanvasTextPrivate, 1); + text->priv->bitmap.buffer = NULL; + text->priv->render_dirty = 1; +} + +/* Destroy handler for the text item */ +static void +gnome_canvas_text_destroy (GtkObject *object) +{ + GnomeCanvasText *text; + + g_return_if_fail (GNOME_IS_CANVAS_TEXT (object)); + + text = GNOME_CANVAS_TEXT (object); + + /* remember, destroy can be run multiple times! */ + + g_free (text->text); + text->text = NULL; + + if (text->layout) + g_object_unref (G_OBJECT (text->layout)); + text->layout = NULL; + + if (text->font_desc) { + pango_font_description_free (text->font_desc); + text->font_desc = NULL; + } + + if (text->attr_list) + pango_attr_list_unref (text->attr_list); + text->attr_list = NULL; + + if (text->stipple) + g_object_unref (text->stipple); + text->stipple = NULL; + + if (text->priv && text->priv->bitmap.buffer) { + g_free (text->priv->bitmap.buffer); + } + g_free (text->priv); + text->priv = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +get_bounds (GnomeCanvasText *text, double *px1, double *py1, double *px2, double *py2) +{ + GnomeCanvasItem *item; + double wx, wy; + + item = GNOME_CANVAS_ITEM (text); + + /* Get canvas pixel coordinates for text position */ + + + wx = text->x; + wy = text->y; + gnome_canvas_item_i2w (item, &wx, &wy); + gnome_canvas_w2c (item->canvas, wx + text->xofs, wy + text->yofs, &text->cx, &text->cy); + + /* Get canvas pixel coordinates for clip rectangle position */ + + gnome_canvas_w2c (item->canvas, wx, wy, &text->clip_cx, &text->clip_cy); + text->clip_cwidth = text->clip_width * item->canvas->pixels_per_unit; + text->clip_cheight = text->clip_height * item->canvas->pixels_per_unit; + + /* Anchor text */ + + switch (text->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + text->cx -= text->max_width / 2; + text->clip_cx -= text->clip_cwidth / 2; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + text->cx -= text->max_width; + text->clip_cx -= text->clip_cwidth; + break; + + default: + break; + } + + switch (text->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + text->cy -= text->height / 2; + text->clip_cy -= text->clip_cheight / 2; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + text->cy -= text->height; + text->clip_cy -= text->clip_cheight; + break; + + default: + break; + } + + /* Bounds */ + + if (text->clip) { + *px1 = text->clip_cx; + *py1 = text->clip_cy; + *px2 = text->clip_cx + text->clip_cwidth; + *py2 = text->clip_cy + text->clip_cheight; + } else { + *px1 = text->cx; + *py1 = text->cy; + *px2 = text->cx + text->max_width; + *py2 = text->cy + text->height; + } +} + +/* Convenience function to set the text's GC's foreground color */ +static void +set_text_gc_foreground (GnomeCanvasText *text) +{ + GdkColor c; + + if (!text->gc) + return; + + c.pixel = text->pixel; + gdk_gc_set_foreground (text->gc, &c); +} + +/* Sets the stipple pattern for the text */ +static void +set_stipple (GnomeCanvasText *text, GdkBitmap *stipple, int reconfigure) +{ + if (text->stipple && !reconfigure) + g_object_unref (text->stipple); + + text->stipple = stipple; + if (stipple && !reconfigure) + g_object_ref (stipple); + + if (text->gc) { + if (stipple) { + gdk_gc_set_stipple (text->gc, stipple); + gdk_gc_set_fill (text->gc, GDK_STIPPLED); + } else + gdk_gc_set_fill (text->gc, GDK_SOLID); + } +} + +static PangoFontMask +get_property_font_set_mask (guint prop_id) +{ + switch (prop_id) + { + case PROP_FAMILY_SET: + return PANGO_FONT_MASK_FAMILY; + case PROP_STYLE_SET: + return PANGO_FONT_MASK_STYLE; + case PROP_VARIANT_SET: + return PANGO_FONT_MASK_VARIANT; + case PROP_WEIGHT_SET: + return PANGO_FONT_MASK_WEIGHT; + case PROP_STRETCH_SET: + return PANGO_FONT_MASK_STRETCH; + case PROP_SIZE_SET: + return PANGO_FONT_MASK_SIZE; + } + + return 0; +} + +static void +ensure_font (GnomeCanvasText *text) +{ + if (!text->font_desc) + text->font_desc = pango_font_description_new (); +} + +/* Set_arg handler for the text item */ +static void +gnome_canvas_text_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasText *text; + GdkColor color = { 0, 0, 0, 0, }; + GdkColor *pcolor; + gboolean color_changed; + int have_pixel; + PangoAlignment align; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_TEXT (object)); + + item = GNOME_CANVAS_ITEM (object); + text = GNOME_CANVAS_TEXT (object); + + color_changed = FALSE; + have_pixel = FALSE; + + + if (!text->layout) { + + PangoContext *gtk_context, *context; + gtk_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas)); + + if (item->canvas->aa) { + PangoFontMap *fontmap; + PangoLanguage *language; + gint pixels, mm; + double dpi_x; + double dpi_y; + + pixels = gdk_screen_width (); + mm = gdk_screen_width_mm (); + dpi_x = (((double) pixels * 25.4) / (double) mm); + + pixels = gdk_screen_height (); + mm = gdk_screen_height_mm (); + dpi_y = (((double) pixels * 25.4) / (double) mm); + + /* XXX This used to call pango_ft2_get_context(). + * Is there a better way to do this? */ + fontmap = pango_ft2_font_map_new (); + pango_ft2_font_map_set_resolution (PANGO_FT2_FONT_MAP (fontmap), dpi_x, dpi_y); + context = pango_font_map_create_context (fontmap); + + language = pango_context_get_language (gtk_context); + pango_context_set_language (context, language); + pango_context_set_base_dir (context, + pango_context_get_base_dir (gtk_context)); + pango_context_set_font_description (context, + pango_context_get_font_description (gtk_context)); + + } else + context = gtk_context; + + + text->layout = pango_layout_new (context); + + if (item->canvas->aa) + g_object_unref (G_OBJECT (context)); + } + + switch (param_id) { + case PROP_TEXT: + g_free (text->text); + + text->text = g_value_dup_string (value); + pango_layout_set_text (text->layout, text->text, -1); + + text->priv->render_dirty = 1; + break; + + case PROP_MARKUP: + gnome_canvas_text_set_markup (text, + g_value_get_string (value)); + text->priv->render_dirty = 1; + break; + + case PROP_X: + text->x = g_value_get_double (value); + break; + + case PROP_Y: + text->y = g_value_get_double (value); + break; + + case PROP_FONT: { + const char *font_name; + PangoFontDescription *font_desc; + + font_name = g_value_get_string (value); + if (font_name) + font_desc = pango_font_description_from_string (font_name); + else + font_desc = NULL; + + gnome_canvas_text_set_font_desc (text, font_desc); + if (font_desc) + pango_font_description_free (font_desc); + + break; + } + + case PROP_FONT_DESC: + gnome_canvas_text_set_font_desc (text, g_value_peek_pointer (value)); + break; + + case PROP_FAMILY: + case PROP_STYLE: + case PROP_VARIANT: + case PROP_WEIGHT: + case PROP_STRETCH: + case PROP_SIZE: + case PROP_SIZE_POINTS: + ensure_font (text); + + switch (param_id) { + case PROP_FAMILY: + pango_font_description_set_family (text->font_desc, + g_value_get_string (value)); + break; + case PROP_STYLE: + pango_font_description_set_style (text->font_desc, + g_value_get_enum (value)); + break; + case PROP_VARIANT: + pango_font_description_set_variant (text->font_desc, + g_value_get_enum (value)); + break; + case PROP_WEIGHT: + pango_font_description_set_weight (text->font_desc, + g_value_get_int (value)); + break; + case PROP_STRETCH: + pango_font_description_set_stretch (text->font_desc, + g_value_get_enum (value)); + break; + case PROP_SIZE: + /* FIXME: This is bogus! It should be pixels, not points/PANGO_SCALE! */ + pango_font_description_set_size (text->font_desc, + g_value_get_int (value)); + break; + case PROP_SIZE_POINTS: + pango_font_description_set_size (text->font_desc, + g_value_get_double (value) * PANGO_SCALE); + break; + } + + gnome_canvas_text_apply_font_desc (text); + text->priv->render_dirty = 1; + break; + + case PROP_FAMILY_SET: + case PROP_STYLE_SET: + case PROP_VARIANT_SET: + case PROP_WEIGHT_SET: + case PROP_STRETCH_SET: + case PROP_SIZE_SET: + if (!g_value_get_boolean (value) && text->font_desc) + pango_font_description_unset_fields (text->font_desc, + get_property_font_set_mask (param_id)); + break; + + case PROP_SCALE: + text->scale = g_value_get_double (value); + text->scale_set = TRUE; + + gnome_canvas_text_apply_font_desc (text); + text->priv->render_dirty = 1; + break; + + case PROP_SCALE_SET: + text->scale_set = g_value_get_boolean (value); + + gnome_canvas_text_apply_font_desc (text); + text->priv->render_dirty = 1; + break; + + case PROP_UNDERLINE: + text->underline = g_value_get_enum (value); + text->underline_set = TRUE; + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_UNDERLINE_SET: + text->underline_set = g_value_get_boolean (value); + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_STRIKETHROUGH: + text->strikethrough = g_value_get_boolean (value); + text->strike_set = TRUE; + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_STRIKETHROUGH_SET: + text->strike_set = g_value_get_boolean (value); + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_RISE: + text->rise = g_value_get_int (value); + text->rise_set = TRUE; + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_RISE_SET: + text->rise_set = TRUE; + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_ATTRIBUTES: + if (text->attr_list) + pango_attr_list_unref (text->attr_list); + + text->attr_list = g_value_peek_pointer (value); + pango_attr_list_ref (text->attr_list); + + gnome_canvas_text_apply_attributes (text); + text->priv->render_dirty = 1; + break; + + case PROP_ANCHOR: + text->anchor = g_value_get_enum (value); + break; + + case PROP_JUSTIFICATION: + text->justification = g_value_get_enum (value); + + switch (text->justification) { + case GTK_JUSTIFY_LEFT: + align = PANGO_ALIGN_LEFT; + break; + case GTK_JUSTIFY_CENTER: + align = PANGO_ALIGN_CENTER; + break; + case GTK_JUSTIFY_RIGHT: + align = PANGO_ALIGN_RIGHT; + break; + default: + /* GTK_JUSTIFY_FILL isn't supported yet. */ + align = PANGO_ALIGN_LEFT; + break; + } + pango_layout_set_alignment (text->layout, align); + text->priv->render_dirty = 1; + break; + + case PROP_CLIP_WIDTH: + text->clip_width = fabs (g_value_get_double (value)); + text->priv->render_dirty = 1; + break; + + case PROP_CLIP_HEIGHT: + text->clip_height = fabs (g_value_get_double (value)); + text->priv->render_dirty = 1; + break; + + case PROP_CLIP: + text->clip = g_value_get_boolean (value); + text->priv->render_dirty = 1; + break; + + case PROP_X_OFFSET: + text->xofs = g_value_get_double (value); + break; + + case PROP_Y_OFFSET: + text->yofs = g_value_get_double (value); + break; + + case PROP_FILL_COLOR: { + const char *color_name; + + color_name = g_value_get_string (value); + if (color_name) { + gdk_color_parse (color_name, &color); + + text->rgba = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + color_changed = TRUE; + } + text->priv->render_dirty = 1; + break; + } + + case PROP_FILL_COLOR_GDK: + pcolor = g_value_get_boxed (value); + if (pcolor) { + GdkColormap *colormap; + + color = *pcolor; + colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas)); + gdk_rgb_find_color (colormap, &color); + have_pixel = TRUE; + } + + text->rgba = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8| + (color.blue & 0xff00) | + 0xff); + color_changed = TRUE; + break; + + case PROP_FILL_COLOR_RGBA: + text->rgba = g_value_get_uint (value); + color_changed = TRUE; + text->priv->render_dirty = 1; + break; + + case PROP_FILL_STIPPLE: + set_stipple (text, (GdkBitmap *)g_value_get_object (value), FALSE); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + if (color_changed) { + if (have_pixel) + text->pixel = color.pixel; + else + text->pixel = gnome_canvas_get_color_pixel (item->canvas, text->rgba); + + if (!item->canvas->aa) + set_text_gc_foreground (text); + } + + /* Calculate text dimensions */ + + if (text->layout) + pango_layout_get_pixel_size (text->layout, + &text->max_width, + &text->height); + else { + text->max_width = 0; + text->height = 0; + } + + gnome_canvas_item_request_update (item); +} + +/* Get_arg handler for the text item */ +static void +gnome_canvas_text_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasText *text; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_TEXT (object)); + + text = GNOME_CANVAS_TEXT (object); + + switch (param_id) { + case PROP_TEXT: + g_value_set_string (value, text->text); + break; + + case PROP_X: + g_value_set_double (value, text->x); + break; + + case PROP_Y: + g_value_set_double (value, text->y); + break; + + case PROP_FONT: + case PROP_FONT_DESC: + case PROP_FAMILY: + case PROP_STYLE: + case PROP_VARIANT: + case PROP_WEIGHT: + case PROP_STRETCH: + case PROP_SIZE: + case PROP_SIZE_POINTS: + ensure_font (text); + + switch (param_id) { + case PROP_FONT: + { + /* FIXME GValue imposes a totally gratuitous string copy + * here, we could just hand off string ownership + */ + gchar *str; + + str = pango_font_description_to_string (text->font_desc); + g_value_set_string (value, str); + g_free (str); + + break; + } + + case PROP_FONT_DESC: + g_value_set_boxed (value, text->font_desc); + break; + + case PROP_FAMILY: + g_value_set_string (value, pango_font_description_get_family (text->font_desc)); + break; + + case PROP_STYLE: + g_value_set_enum (value, pango_font_description_get_style (text->font_desc)); + break; + + case PROP_VARIANT: + g_value_set_enum (value, pango_font_description_get_variant (text->font_desc)); + break; + + case PROP_WEIGHT: + g_value_set_int (value, pango_font_description_get_weight (text->font_desc)); + break; + + case PROP_STRETCH: + g_value_set_enum (value, pango_font_description_get_stretch (text->font_desc)); + break; + + case PROP_SIZE: + g_value_set_int (value, pango_font_description_get_size (text->font_desc)); + break; + + case PROP_SIZE_POINTS: + g_value_set_double (value, ((double)pango_font_description_get_size (text->font_desc)) / (double)PANGO_SCALE); + break; + } + break; + + case PROP_FAMILY_SET: + case PROP_STYLE_SET: + case PROP_VARIANT_SET: + case PROP_WEIGHT_SET: + case PROP_STRETCH_SET: + case PROP_SIZE_SET: + { + PangoFontMask set_mask = text->font_desc ? pango_font_description_get_set_fields (text->font_desc) : 0; + PangoFontMask test_mask = get_property_font_set_mask (param_id); + g_value_set_boolean (value, (set_mask & test_mask) != 0); + + break; + } + + case PROP_SCALE: + g_value_set_double (value, text->scale); + break; + case PROP_SCALE_SET: + g_value_set_boolean (value, text->scale_set); + break; + + case PROP_UNDERLINE: + g_value_set_enum (value, text->underline); + break; + case PROP_UNDERLINE_SET: + g_value_set_boolean (value, text->underline_set); + break; + + case PROP_STRIKETHROUGH: + g_value_set_boolean (value, text->strikethrough); + break; + case PROP_STRIKETHROUGH_SET: + g_value_set_boolean (value, text->strike_set); + break; + + case PROP_RISE: + g_value_set_int (value, text->rise); + break; + case PROP_RISE_SET: + g_value_set_boolean (value, text->rise_set); + break; + + case PROP_ATTRIBUTES: + g_value_set_boxed (value, text->attr_list); + break; + + case PROP_ANCHOR: + g_value_set_enum (value, text->anchor); + break; + + case PROP_JUSTIFICATION: + g_value_set_enum (value, text->justification); + break; + + case PROP_CLIP_WIDTH: + g_value_set_double (value, text->clip_width); + break; + + case PROP_CLIP_HEIGHT: + g_value_set_double (value, text->clip_height); + break; + + case PROP_CLIP: + g_value_set_boolean (value, text->clip); + break; + + case PROP_X_OFFSET: + g_value_set_double (value, text->xofs); + break; + + case PROP_Y_OFFSET: + g_value_set_double (value, text->yofs); + break; + + case PROP_FILL_COLOR: + g_value_take_string (value, + g_strdup_printf ("#%02x%02x%02x", + text->rgba >> 24, + (text->rgba >> 16) & 0xff, + (text->rgba >> 8) & 0xff)); + break; + + case PROP_FILL_COLOR_GDK: { + GnomeCanvas *canvas = GNOME_CANVAS_ITEM (text)->canvas; + GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas)); + GdkColor color; + + gdk_colormap_query_color (colormap, text->pixel, &color); + g_value_set_boxed (value, &color); + break; + } + case PROP_FILL_COLOR_RGBA: + g_value_set_uint (value, text->rgba); + break; + + case PROP_FILL_STIPPLE: + g_value_set_object (value, text->stipple); + break; + + case PROP_TEXT_WIDTH: + g_value_set_double (value, text->max_width / text->item.canvas->pixels_per_unit); + break; + + case PROP_TEXT_HEIGHT: + g_value_set_double (value, text->height / text->item.canvas->pixels_per_unit); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +/* */ +static void +gnome_canvas_text_apply_font_desc (GnomeCanvasText *text) +{ + PangoFontDescription *font_desc = + pango_font_description_copy ( + GTK_WIDGET (GNOME_CANVAS_ITEM (text)->canvas)->style->font_desc); + + if (text->font_desc) + pango_font_description_merge (font_desc, text->font_desc, TRUE); + + pango_layout_set_font_description (text->layout, font_desc); + pango_font_description_free (font_desc); +} + +static void +add_attr (PangoAttrList *attr_list, + PangoAttribute *attr) +{ + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attr_list, attr); +} + +/* */ +static void +gnome_canvas_text_apply_attributes (GnomeCanvasText *text) +{ + PangoAttrList *attr_list; + + if (text->attr_list) + attr_list = pango_attr_list_copy (text->attr_list); + else + attr_list = pango_attr_list_new (); + + if (text->underline_set) + add_attr (attr_list, pango_attr_underline_new (text->underline)); + if (text->strike_set) + add_attr (attr_list, pango_attr_strikethrough_new (text->strikethrough)); + if (text->rise_set) + add_attr (attr_list, pango_attr_rise_new (text->rise)); + + pango_layout_set_attributes (text->layout, attr_list); + pango_attr_list_unref (attr_list); +} + +static void +gnome_canvas_text_set_font_desc (GnomeCanvasText *text, + PangoFontDescription *font_desc) +{ + if (text->font_desc) + pango_font_description_free (text->font_desc); + + if (font_desc) + text->font_desc = pango_font_description_copy (font_desc); + else + text->font_desc = NULL; + + gnome_canvas_text_apply_font_desc (text); + text->priv->render_dirty = 1; +} + +/* Setting the text from a Pango markup string */ +static void +gnome_canvas_text_set_markup (GnomeCanvasText *textitem, + const gchar *markup) +{ + PangoAttrList *attr_list = NULL; + gchar *text = NULL; + GError *error = NULL; + + if (markup && !pango_parse_markup (markup, -1, + 0, + &attr_list, &text, NULL, + &error)) + { + g_warning ("Failed to set cell text from markup due to error parsing markup: %s", + error->message); + g_error_free (error); + return; + } + + g_free (textitem->text); + if (textitem->attr_list) + pango_attr_list_unref (textitem->attr_list); + + textitem->text = text; + textitem->attr_list = attr_list; + + pango_layout_set_text (textitem->layout, text, -1); + + gnome_canvas_text_apply_attributes (textitem); +} + +/* Update handler for the text item */ +static void +gnome_canvas_text_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasText *text; + double x1, y1, x2, y2; + + text = GNOME_CANVAS_TEXT (item); + + if (parent_class->update) + (* parent_class->update) (item, affine, clip_path, flags); + + set_text_gc_foreground (text); + set_stipple (text, text->stipple, TRUE); + get_bounds (text, &x1, &y1, &x2, &y2); + + gnome_canvas_update_bbox (item, + floor (x1), floor (y1), + ceil (x2), ceil (y2)); +} + +/* Realize handler for the text item */ +static void +gnome_canvas_text_realize (GnomeCanvasItem *item) +{ + GnomeCanvasText *text; + + text = GNOME_CANVAS_TEXT (item); + + if (parent_class->realize) + (* parent_class->realize) (item); + + text->gc = gdk_gc_new (item->canvas->layout.bin_window); +} + +/* Unrealize handler for the text item */ +static void +gnome_canvas_text_unrealize (GnomeCanvasItem *item) +{ + GnomeCanvasText *text; + + text = GNOME_CANVAS_TEXT (item); + + g_object_unref (text->gc); + text->gc = NULL; + + if (parent_class->unrealize) + (* parent_class->unrealize) (item); +} + +/* Draw handler for the text item */ +static void +gnome_canvas_text_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + GnomeCanvasText *text; + GdkRectangle rect; + + text = GNOME_CANVAS_TEXT (item); + + if (!text->text) + return; + + if (text->clip) { + rect.x = text->clip_cx - x; + rect.y = text->clip_cy - y; + rect.width = text->clip_cwidth; + rect.height = text->clip_cheight; + + gdk_gc_set_clip_rectangle (text->gc, &rect); + } + + if (text->stipple) + gnome_canvas_set_stipple_origin (item->canvas, text->gc); + + + gdk_draw_layout (drawable, text->gc, text->cx - x, text->cy - y, text->layout); + + if (text->clip) + gdk_gc_set_clip_rectangle (text->gc, NULL); +} + + +/* Render handler for the text item */ +static void +gnome_canvas_text_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) +{ + GnomeCanvasText *text; + guint32 fg_color; + int render_x = 0, render_y = 0; /* offsets for text rendering, + * for clipping rectangles */ + int x, y; + int w, h; + guchar *dst, *src; + int src_dx, src_dy; + int i, alpha; + int bm_rows, bm_width; + + text = GNOME_CANVAS_TEXT (item); + + if (!text->text) + return; + + fg_color = text->rgba; + + gnome_canvas_buf_ensure_buf (buf); + + bm_rows = (text->clip) ? text->clip_cheight : text->height; + bm_width = (text->clip) ? text->clip_cwidth : text->max_width; + if(text->priv->render_dirty || + bm_rows != text->priv->bitmap.rows || + bm_width != text->priv->bitmap.width) { + if(text->priv->bitmap.buffer) { + g_free(text->priv->bitmap.buffer); + } + text->priv->bitmap.rows = bm_rows; + text->priv->bitmap.width = bm_width; + text->priv->bitmap.pitch = (text->priv->bitmap.width+3)&~3; + text->priv->bitmap.buffer = g_malloc0 (text->priv->bitmap.rows * text->priv->bitmap.pitch); + text->priv->bitmap.num_grays = 256; + text->priv->bitmap.pixel_mode = ft_pixel_mode_grays; + + /* What this does is when a clipping rectangle is + being used shift the rendering of the text by the + correct amount so that the correct result is + obtained as if all text was rendered, then clipped. + In this sense we can use smaller buffers and less + rendeirng since hopefully FreeType2 checks to see + if the glyph falls in the bounding box before + rasterizing it. */ + + if(text->clip) { + render_x = text->cx - text->clip_cx; + render_y = text->cy - text->clip_cy; + } + + pango_ft2_render_layout (&text->priv->bitmap, text->layout, render_x, render_y); + + text->priv->render_dirty = 0; + } + + if (text->clip) { + x = text->clip_cx - buf->rect.x0; + y = text->clip_cy - buf->rect.y0; + } else { + x = text->cx - buf->rect.x0; + y = text->cy - buf->rect.y0; + } + + w = text->priv->bitmap.width; + h = text->priv->bitmap.rows; + + src_dx = src_dy = 0; + + if (x + w > buf->rect.x1 - buf->rect.x0) { + w = buf->rect.x1 - buf->rect.x0 - x; + } + + if (y + h > buf->rect.y1 - buf->rect.y0) { + h = buf->rect.y1 - buf->rect.y0 - y; + } + + if (x < 0) { + w -= - x; + src_dx += - x; + x = 0; + } + + if (y < 0) { + h -= -y; + src_dy += - y; + y = 0; + } + + dst = buf->buf + y * buf->buf_rowstride + x * 3; + src = text->priv->bitmap.buffer + + src_dy * text->priv->bitmap.pitch + src_dx; + while (h-- > 0) { + i = w; + while (i-- > 0) { + /* FIXME: Do the libart thing instead of divide by 255 */ + alpha = ((fg_color & 0xff) * (*src)) / 255; + dst[0] = (dst[0] * (255 - alpha) + ((fg_color >> 24) & 0xff) * alpha) / 255; + dst[1] = (dst[1] * (255 - alpha) + ((fg_color >> 16) & 0xff) * alpha) / 255; + dst[2] = (dst[2] * (255 - alpha) + ((fg_color >> 8) & 0xff) * alpha) / 255; + dst += 3; + src += 1; + } + dst += buf->buf_rowstride - w*3; + src += text->priv->bitmap.pitch - w; + } + + buf->is_bg = 0; + return; +} + +/* Point handler for the text item */ +static double +gnome_canvas_text_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item) +{ + GnomeCanvasText *text; + PangoLayoutIter *iter; + int x1, y1, x2, y2; + int dx, dy; + double dist, best; + + text = GNOME_CANVAS_TEXT (item); + + *actual_item = item; + + /* The idea is to build bounding rectangles for each of the lines of + * text (clipped by the clipping rectangle, if it is activated) and see + * whether the point is inside any of these. If it is, we are done. + * Otherwise, calculate the distance to the nearest rectangle. + */ + + best = 1.0e36; + + iter = pango_layout_get_iter (text->layout); + do { + PangoRectangle log_rect; + + pango_layout_iter_get_line_extents (iter, NULL, &log_rect); + + x1 = text->cx + PANGO_PIXELS (log_rect.x); + y1 = text->cy + PANGO_PIXELS (log_rect.y); + x2 = x1 + PANGO_PIXELS (log_rect.width); + y2 = y1 + PANGO_PIXELS (log_rect.height); + + if (text->clip) { + if (x1 < text->clip_cx) + x1 = text->clip_cx; + + if (y1 < text->clip_cy) + y1 = text->clip_cy; + + if (x2 > (text->clip_cx + text->clip_width)) + x2 = text->clip_cx + text->clip_width; + + if (y2 > (text->clip_cy + text->clip_height)) + y2 = text->clip_cy + text->clip_height; + + if ((x1 >= x2) || (y1 >= y2)) + continue; + } + + /* Calculate distance from point to rectangle */ + + if (cx < x1) + dx = x1 - cx; + else if (cx >= x2) + dx = cx - x2 + 1; + else + dx = 0; + + if (cy < y1) + dy = y1 - cy; + else if (cy >= y2) + dy = cy - y2 + 1; + else + dy = 0; + + if ((dx == 0) && (dy == 0)) { + pango_layout_iter_free(iter); + return 0.0; + } + + dist = sqrt (dx * dx + dy * dy); + if (dist < best) + best = dist; + + } while (pango_layout_iter_next_line(iter)); + + pango_layout_iter_free(iter); + + return best / item->canvas->pixels_per_unit; +} + +/* Bounds handler for the text item */ +static void +gnome_canvas_text_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasText *text; + double width, height; + + text = GNOME_CANVAS_TEXT (item); + + *x1 = text->x; + *y1 = text->y; + + if (text->clip) { + width = text->clip_width; + height = text->clip_height; + } else { + width = text->max_width / item->canvas->pixels_per_unit; + height = text->height / item->canvas->pixels_per_unit; + } + + switch (text->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + *x1 -= width / 2.0; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + *x1 -= width; + break; + + default: + break; + } + + switch (text->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + *y1 -= height / 2.0; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + *y1 -= height; + break; + + default: + break; + } + + *x2 = *x1 + width; + *y2 = *y1 + height; +} diff --git a/libgnomecanvas/gnome-canvas-text.h b/libgnomecanvas/gnome-canvas-text.h new file mode 100644 index 0000000000..ed86633371 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-text.h @@ -0,0 +1,170 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Text item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + * Port to Pango co-done by Gergõ Érdi <cactus@cactus.rulez.org> + */ + +#ifndef GNOME_CANVAS_TEXT_H +#define GNOME_CANVAS_TEXT_H + + +#include <libgnomecanvas/gnome-canvas.h> + + +G_BEGIN_DECLS + + +/* Text item for the canvas. Text items are positioned by an anchor point and an anchor direction. + * + * A clipping rectangle may be specified for the text. The rectangle is anchored at the text's anchor + * point, and is specified by clipping width and height parameters. If the clipping rectangle is + * enabled, it will clip the text. + * + * In addition, x and y offset values may be specified. These specify an offset from the anchor + * position. If used in conjunction with the clipping rectangle, these could be used to implement + * simple scrolling of the text within the clipping rectangle. + * + * Properties marked with [*] also have _set properties associated + * with them, that determine if the specified value should be used + * instead of the default (style-defined) values + * + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * text string RW The string of the text label + * markup string W A Pango markup string for the text label + * + * x double RW X coordinate of anchor point + * y double RW Y coordinate of anchor point + * + * font string W A string describing the font + * font_desc PangoFontDescription* RW Pointer to a PangoFontDescriptor + * attributes PangoAttrList* RW Pointer to a Pango attribute list + * style PangoStyle RW Pango style of font to use [*] + * variant PangoVariant RW Pango variant of font to use [*] + * weight int RW Pango weight of font to use [*] + * stretch PangoStretch RW Pango stretch of font to use [*] + * size int RW Size (in pixels) of font [*] + * size_points double RW Size (in points) of font + * scale double RW Ratio to scale font [*] + * + * anchor GtkAnchorType RW Anchor side for the text + * justification GtkJustification RW Justification for multiline text + * clip_width double RW Width of clip rectangle + * clip_height double RW Height of clip rectangle + * clip boolean RW Use clipping rectangle? + * x_offset double RW Horizontal offset distance from anchor position + * y_offset double RW Vertical offset distance from anchor position + * + * text_width double R Used to query the width of the rendered text + * text_height double R Used to query the rendered height of the text + * + * fill_color string W X color specification for text + * fill_color_gdk GdkColor* RW Pointer to an allocated GdkColor + * fill_color_rgba guint RW RGBA value used for AA color. + * fill_stipple GdkBitmap* RW Stipple pattern for filling the text + */ + +#define GNOME_TYPE_CANVAS_TEXT (gnome_canvas_text_get_type ()) +#define GNOME_CANVAS_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_TEXT, GnomeCanvasText)) +#define GNOME_CANVAS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_TEXT, GnomeCanvasTextClass)) +#define GNOME_IS_CANVAS_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_TEXT)) +#define GNOME_IS_CANVAS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_TEXT)) +#define GNOME_CANVAS_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_TEXT, GnomeCanvasTextClass)) + + +typedef struct _GnomeCanvasText GnomeCanvasText; +typedef struct _GnomeCanvasTextClass GnomeCanvasTextClass; + +typedef struct _GnomeCanvasTextPrivate GnomeCanvasTextPrivate; + +struct _GnomeCanvasText { + GnomeCanvasItem item; + + PangoFontDescription *font_desc; /* Font description for text */ + PangoAttrList *attr_list; /* Attribute list of the text (caching) */ + PangoUnderline underline; + gboolean strikethrough; + int rise; + double scale; + + char *text; /* Text to display */ + GdkBitmap *stipple; /* Stipple for text */ + GdkGC *gc; /* GC for drawing text */ + PangoLayout *layout; /* The PangoLayout containing the text */ + + gulong pixel; /* Fill color */ + + double x, y; /* Position at anchor */ + + double clip_width; /* Width of optional clip rectangle */ + double clip_height; /* Height of optional clip rectangle */ + + double xofs, yofs; /* Text offset distance from anchor position */ + + double affine[6]; /* The item -> canvas affine */ /*AA*/ + + GtkAnchorType anchor; /* Anchor side for text */ + GtkJustification justification; /* Justification for text */ + + int cx, cy; /* Top-left canvas coordinates for text */ + int clip_cx, clip_cy; /* Top-left canvas coordinates for clip rectangle */ + int clip_cwidth, clip_cheight; /* Size of clip rectangle in pixels */ + int max_width; /* Maximum width of text lines */ + int height; /* Rendered text height in pixels */ + + guint32 rgba; /* RGBA color for text */ /*AA*/ + + guint clip : 1; /* Use clip rectangle? */ + + guint underline_set : 1; /* Apply specified underline style? */ + guint strike_set : 1; /* Apply specified strikethrough style? */ + guint rise_set : 1; /* Apply specified ascension/descension? */ + + guint scale_set : 1; /* Apply specified font scaling ratio? */ + + GnomeCanvasTextPrivate *priv; +}; + +struct _GnomeCanvasTextClass { + GnomeCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_text_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-util.c b/libgnomecanvas/gnome-canvas-util.c new file mode 100644 index 0000000000..d306292144 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-util.c @@ -0,0 +1,700 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Miscellaneous utility functions for the GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#include <config.h> + +/* needed for M_PI_2 under 'gcc -ansi -predantic' on GNU/Linux */ +#ifndef _BSD_SOURCE +# define _BSD_SOURCE 1 +#endif +#include <sys/types.h> + +#include <glib.h> +#include <math.h> +#include "gnome-canvas.h" +#include "gnome-canvas-util.h" +#include <libart_lgpl/art_uta.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_ops.h> +#include <libart_lgpl/art_rgb.h> +#include <libart_lgpl/art_rgb_svp.h> +#include <libart_lgpl/art_uta_svp.h> +#include <libart_lgpl/art_rect_svp.h> + +/** + * gnome_canvas_points_new: + * @num_points: The number of points to allocate space for in the array. + * + * Creates a structure that should be used to pass an array of points to + * items. + * + * Return value: A newly-created array of points. It should be filled in + * by the user. + **/ +GnomeCanvasPoints * +gnome_canvas_points_new (int num_points) +{ + GnomeCanvasPoints *points; + + g_return_val_if_fail (num_points > 1, NULL); + + points = g_new (GnomeCanvasPoints, 1); + points->num_points = num_points; + points->coords = g_new (double, 2 * num_points); + points->ref_count = 1; + + return points; +} + +/** + * gnome_canvas_points_ref: + * @points: A canvas points structure. + * + * Increases the reference count of the specified points structure. + * + * Return value: The canvas points structure itself. + **/ +GnomeCanvasPoints * +gnome_canvas_points_ref (GnomeCanvasPoints *points) +{ + g_return_val_if_fail (points != NULL, NULL); + + points->ref_count += 1; + return points; +} + +/** + * gnome_canvas_points_free: + * @points: A canvas points structure. + * + * Decreases the reference count of the specified points structure. If it + * reaches zero, then the structure is freed. + **/ +void +gnome_canvas_points_free (GnomeCanvasPoints *points) +{ + g_return_if_fail (points != NULL); + + points->ref_count -= 1; + if (points->ref_count == 0) { + g_free (points->coords); + g_free (points); + } +} + +/** + * gnome_canvas_get_miter_points: + * @x1: X coordinate of the first point + * @y1: Y coordinate of the first point + * @x2: X coordinate of the second (angle) point + * @y2: Y coordinate of the second (angle) point + * @x3: X coordinate of the third point + * @y3: Y coordinate of the third point + * @width: Width of the line + * @mx1: The X coordinate of the first miter point is returned here. + * @my1: The Y coordinate of the first miter point is returned here. + * @mx2: The X coordinate of the second miter point is returned here. + * @my2: The Y coordinate of the second miter point is returned here. + * + * Given three points forming an angle, computes the coordinates of the inside + * and outside points of the mitered corner formed by a line of a given width at + * that angle. + * + * Return value: FALSE if the angle is less than 11 degrees (this is the same + * threshold as X uses. If this occurs, the return points are not modified. + * Otherwise, returns TRUE. + **/ +int +gnome_canvas_get_miter_points (double x1, double y1, double x2, double y2, double x3, double y3, + double width, + double *mx1, double *my1, double *mx2, double *my2) +{ + double theta1; /* angle of segment p2-p1 */ + double theta2; /* angle of segment p2-p3 */ + double theta; /* angle between line segments */ + double theta3; /* angle that bisects theta1 and theta2 and points to p1 */ + double dist; /* distance of miter points from p2 */ + double dx, dy; /* x and y offsets corresponding to dist */ + +#define ELEVEN_DEGREES (11.0 * G_PI / 180.0) + + if (y2 == y1) + theta1 = (x2 < x1) ? 0.0 : G_PI; + else if (x2 == x1) + theta1 = (y2 < y1) ? G_PI_2 : -G_PI_2; + else + theta1 = atan2 (y1 - y2, x1 - x2); + + if (y3 == y2) + theta2 = (x3 > x2) ? 0 : G_PI; + else if (x3 == x2) + theta2 = (y3 > y2) ? G_PI_2 : -G_PI_2; + else + theta2 = atan2 (y3 - y2, x3 - x2); + + theta = theta1 - theta2; + + if (theta > G_PI) + theta -= 2.0 * G_PI; + else if (theta < -G_PI) + theta += 2.0 * G_PI; + + if ((theta < ELEVEN_DEGREES) && (theta > -ELEVEN_DEGREES)) + return FALSE; + + dist = 0.5 * width / sin (0.5 * theta); + if (dist < 0.0) + dist = -dist; + + theta3 = (theta1 + theta2) / 2.0; + if (sin (theta3 - (theta1 + G_PI)) < 0.0) + theta3 += G_PI; + + dx = dist * cos (theta3); + dy = dist * sin (theta3); + + *mx1 = x2 + dx; + *mx2 = x2 - dx; + *my1 = y2 + dy; + *my2 = y2 - dy; + + return TRUE; +} + +/** + * gnome_canvas_get_butt_points: + * @x1: X coordinate of first point in the line + * @y1: Y cooordinate of first point in the line + * @x2: X coordinate of second point (endpoint) of the line + * @y2: Y coordinate of second point (endpoint) of the line + * @width: Width of the line + * @project: Whether the butt points should project out by width/2 distance + * @bx1: X coordinate of first butt point is returned here + * @by1: Y coordinate of first butt point is returned here + * @bx2: X coordinate of second butt point is returned here + * @by2: Y coordinate of second butt point is returned here + * + * Computes the butt points of a line segment. + **/ +void +gnome_canvas_get_butt_points (double x1, double y1, double x2, double y2, + double width, int project, + double *bx1, double *by1, double *bx2, double *by2) +{ + double length; + double dx, dy; + + width *= 0.5; + dx = x2 - x1; + dy = y2 - y1; + length = sqrt (dx * dx + dy * dy); + + if (length < GNOME_CANVAS_EPSILON) { + *bx1 = *bx2 = x2; + *by1 = *by2 = y2; + } else { + dx = -width * (y2 - y1) / length; + dy = width * (x2 - x1) / length; + + *bx1 = x2 + dx; + *bx2 = x2 - dx; + *by1 = y2 + dy; + *by2 = y2 - dy; + + if (project) { + *bx1 += dy; + *bx2 += dy; + *by1 -= dx; + *by2 -= dx; + } + } +} + +/** + * gnome_canvas_polygon_to_point: + * @poly: Vertices of the polygon. X coordinates are in the even indices, and Y + * coordinates are in the odd indices + * @num_points: Number of points in the polygon + * @x: X coordinate of the point + * @y: Y coordinate of the point + * + * Computes the distance between a point and a polygon. + * + * Return value: The distance from the point to the polygon, or zero if the + * point is inside the polygon. + **/ +double +gnome_canvas_polygon_to_point (double *poly, int num_points, double x, double y) +{ + double best; + int intersections; + int i; + double *p; + double dx, dy; + + /* Iterate through all the edges in the polygon, updating best and intersections. + * + * When computing intersections, include left X coordinate of line within its range, but not + * Y coordinate. Otherwise if the point lies exactly below a vertex we'll count it as two + * intersections. + */ + + best = 1.0e36; + intersections = 0; + + for (i = num_points, p = poly; i > 1; i--, p += 2) { + double px, py, dist; + + /* Compute the point on the current edge closest to the point and update the + * intersection count. This must be done separately for vertical edges, horizontal + * edges, and others. + */ + + if (p[2] == p[0]) { + /* Vertical edge */ + + px = p[0]; + + if (p[1] >= p[3]) { + py = MIN (p[1], y); + py = MAX (py, p[3]); + } else { + py = MIN (p[3], y); + py = MAX (py, p[1]); + } + } else if (p[3] == p[1]) { + /* Horizontal edge */ + + py = p[1]; + + if (p[0] >= p[2]) { + px = MIN (p[0], x); + px = MAX (px, p[2]); + + if ((y < py) && (x < p[0]) && (x >= p[2])) + intersections++; + } else { + px = MIN (p[2], x); + px = MAX (px, p[0]); + + if ((y < py) && (x < p[2]) && (x >= p[0])) + intersections++; + } + } else { + double m1, b1, m2, b2; + int lower; + + /* Diagonal edge. Convert the edge to a line equation (y = m1*x + b1), then + * compute a line perpendicular to this edge but passing through the point, + * (y = m2*x + b2). + */ + + m1 = (p[3] - p[1]) / (p[2] - p[0]); + b1 = p[1] - m1 * p[0]; + + m2 = -1.0 / m1; + b2 = y - m2 * x; + + px = (b2 - b1) / (m1 - m2); + py = m1 * px + b1; + + if (p[0] > p[2]) { + if (px > p[0]) { + px = p[0]; + py = p[1]; + } else if (px < p[2]) { + px = p[2]; + py = p[3]; + } + } else { + if (px > p[2]) { + px = p[2]; + py = p[3]; + } else if (px < p[0]) { + px = p[0]; + py = p[1]; + } + } + + lower = (m1 * x + b1) > y; + + if (lower && (x >= MIN (p[0], p[2])) && (x < MAX (p[0], p[2]))) + intersections++; + } + + /* Compute the distance to the closest point, and see if that is the best so far */ + + dx = x - px; + dy = y - py; + dist = sqrt (dx * dx + dy * dy); + if (dist < best) + best = dist; + } + + /* We've processed all the points. If the number of intersections is odd, the point is + * inside the polygon. + */ + + if (intersections & 0x1) + return 0.0; + else + return best; +} + +/* Here are some helper functions for aa rendering: */ + +/** + * gnome_canvas_render_svp: + * @buf: the canvas buffer to render over + * @svp: the vector path to render + * @rgba: the rgba color to render + * + * Render the svp over the buf. + **/ +void +gnome_canvas_render_svp (GnomeCanvasBuf *buf, ArtSVP *svp, guint32 rgba) +{ + guint32 fg_color, bg_color; + int alpha; + + if (buf->is_bg) { + bg_color = buf->bg_color; + alpha = rgba & 0xff; + if (alpha == 0xff) + fg_color = rgba >> 8; + else { + /* composite over background color */ + int bg_r, bg_g, bg_b; + int fg_r, fg_g, fg_b; + int tmp; + + bg_r = (bg_color >> 16) & 0xff; + fg_r = (rgba >> 24) & 0xff; + tmp = (fg_r - bg_r) * alpha; + fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8); + + bg_g = (bg_color >> 8) & 0xff; + fg_g = (rgba >> 16) & 0xff; + tmp = (fg_g - bg_g) * alpha; + fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8); + + bg_b = bg_color & 0xff; + fg_b = (rgba >> 8) & 0xff; + tmp = (fg_b - bg_b) * alpha; + fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8); + + fg_color = (fg_r << 16) | (fg_g << 8) | fg_b; + } + art_rgb_svp_aa (svp, + buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1, + fg_color, bg_color, + buf->buf, buf->buf_rowstride, + NULL); + buf->is_bg = 0; + buf->is_buf = 1; + } else { + art_rgb_svp_alpha (svp, + buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1, + rgba, + buf->buf, buf->buf_rowstride, + NULL); + } +} + +/** + * gnome_canvas_update_svp: + * @canvas: the canvas containing the svp that needs updating. + * @p_svp: a pointer to the existing svp + * @new_svp: the new svp + * + * Sets the svp to the new value, requesting repaint on what's changed. This + * function takes responsibility for freeing new_svp. + **/ +void +gnome_canvas_update_svp (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp) +{ + ArtSVP *old_svp; + ArtSVP *diff G_GNUC_UNUSED; + ArtUta *repaint_uta; + + old_svp = *p_svp; + + if (old_svp != NULL) { + ArtDRect bb; + art_drect_svp (&bb, old_svp); + if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) { + repaint_uta = art_uta_from_svp (old_svp); + gnome_canvas_request_redraw_uta (canvas, repaint_uta); + } else { + ArtIRect ib; + art_drect_to_irect (&ib, &bb); + gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1); + } + art_svp_free (old_svp); + } + + if (new_svp != NULL) { + ArtDRect bb; + art_drect_svp (&bb, new_svp); + if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) { + repaint_uta = art_uta_from_svp (new_svp); + gnome_canvas_request_redraw_uta (canvas, repaint_uta); + } else { + ArtIRect ib; + art_drect_to_irect (&ib, &bb); + gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1); + } + } + + *p_svp = new_svp; +} + +/** + * gnome_canvas_update_svp_clip: + * @canvas: the canvas containing the svp that needs updating. + * @p_svp: a pointer to the existing svp + * @new_svp: the new svp + * @clip_svp: a clip path, if non-null + * + * Sets the svp to the new value, clipping if necessary, and requesting repaint + * on what's changed. This function takes responsibility for freeing new_svp. + **/ +void +gnome_canvas_update_svp_clip (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp, ArtSVP *clip_svp) +{ + ArtSVP *clipped_svp; + + if (clip_svp != NULL) { + clipped_svp = art_svp_intersect (new_svp, clip_svp); + art_svp_free (new_svp); + } else { + clipped_svp = new_svp; + } + gnome_canvas_update_svp (canvas, p_svp, clipped_svp); +} + +/** + * gnome_canvas_item_reset_bounds: + * @item: A canvas item + * + * Resets the bounding box of a canvas item to an empty rectangle. + **/ +void +gnome_canvas_item_reset_bounds (GnomeCanvasItem *item) +{ + item->x1 = 0.0; + item->y1 = 0.0; + item->x2 = 0.0; + item->y2 = 0.0; +} + +/** + * gnome_canvas_item_update_svp: + * @item: the canvas item containing the svp that needs updating. + * @p_svp: a pointer to the existing svp + * @new_svp: the new svp + * + * Sets the svp to the new value, requesting repaint on what's changed. This + * function takes responsibility for freeing new_svp. This routine also adds the + * svp's bbox to the item's. + **/ +void +gnome_canvas_item_update_svp (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp) +{ + ArtDRect bbox; + + gnome_canvas_update_svp (item->canvas, p_svp, new_svp); + if (new_svp) { + bbox.x0 = item->x1; + bbox.y0 = item->y1; + bbox.x1 = item->x2; + bbox.y1 = item->y2; + art_drect_svp_union (&bbox, new_svp); + item->x1 = bbox.x0; + item->y1 = bbox.y0; + item->x2 = bbox.x1; + item->y2 = bbox.y1; + } +} + +/** + * gnome_canvas_item_update_svp_clip: + * @item: the canvas item containing the svp that needs updating. + * @p_svp: a pointer to the existing svp + * @new_svp: the new svp + * @clip_svp: a clip path, if non-null + * + * Sets the svp to the new value, clipping if necessary, and requesting repaint + * on what's changed. This function takes responsibility for freeing new_svp. + **/ +void +gnome_canvas_item_update_svp_clip (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp, + ArtSVP *clip_svp) +{ + ArtSVP *clipped_svp; + + if (clip_svp != NULL) { + clipped_svp = art_svp_intersect (new_svp, clip_svp); + art_svp_free (new_svp); + } else { + clipped_svp = new_svp; + } + + gnome_canvas_item_update_svp (item, p_svp, clipped_svp); +} + +/** + * gnome_canvas_item_request_redraw_svp + * @item: the item containing the svp + * @svp: the svp that needs to be redrawn + * + * Request redraw of the svp if in aa mode, or the entire item in in xlib mode. + **/ +void +gnome_canvas_item_request_redraw_svp (GnomeCanvasItem *item, const ArtSVP *svp) +{ + GnomeCanvas *canvas; + ArtUta *uta; + + canvas = item->canvas; + if (canvas->aa) { + if (svp != NULL) { + uta = art_uta_from_svp (svp); + gnome_canvas_request_redraw_uta (canvas, uta); + } + } else { + gnome_canvas_request_redraw (canvas, item->x1, item->y1, item->x2, item->y2); + } +} + +/** + * gnome_canvas_update_bbox: + * @item: the canvas item needing update + * @x1: Left coordinate of the new bounding box + * @y1: Top coordinate of the new bounding box + * @x2: Right coordinate of the new bounding box + * @y2: Bottom coordinate of the new bounding box + * + * Sets the bbox to the new value, requesting full repaint. + **/ +void +gnome_canvas_update_bbox (GnomeCanvasItem *item, int x1, int y1, int x2, int y2) +{ + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); + item->x1 = x1; + item->y1 = y1; + item->x2 = x2; + item->y2 = y2; + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); +} + +/** + * gnome_canvas_buf_ensure_buf: + * @buf: the buf that needs to be represened in RGB format + * + * Ensure that the buffer is in RGB format, suitable for compositing. + **/ +void +gnome_canvas_buf_ensure_buf (GnomeCanvasBuf *buf) +{ + guchar *bufptr; + int y; + + if (!buf->is_buf) { + bufptr = buf->buf; + for (y = buf->rect.y0; y < buf->rect.y1; y++) { + art_rgb_fill_run (bufptr, + buf->bg_color >> 16, + (buf->bg_color >> 8) & 0xff, + buf->bg_color & 0xff, + buf->rect.x1 - buf->rect.x0); + bufptr += buf->buf_rowstride; + } + buf->is_buf = 1; + } +} + +/** + * gnome_canvas_join_gdk_to_art + * @gdk_join: a join type, represented in GDK format + * + * Convert from GDK line join specifier to libart. + * + * Return value: The line join specifier in libart format. + **/ +ArtPathStrokeJoinType +gnome_canvas_join_gdk_to_art (GdkJoinStyle gdk_join) +{ + switch (gdk_join) { + case GDK_JOIN_MITER: + return ART_PATH_STROKE_JOIN_MITER; + + case GDK_JOIN_ROUND: + return ART_PATH_STROKE_JOIN_ROUND; + + case GDK_JOIN_BEVEL: + return ART_PATH_STROKE_JOIN_BEVEL; + + default: + g_assert_not_reached (); + return ART_PATH_STROKE_JOIN_MITER; /* shut up the compiler */ + } +} + +/** + * gnome_canvas_cap_gdk_to_art + * @gdk_cap: a cap type, represented in GDK format + * + * Convert from GDK line cap specifier to libart. + * + * Return value: The line cap specifier in libart format. + **/ +ArtPathStrokeCapType +gnome_canvas_cap_gdk_to_art (GdkCapStyle gdk_cap) +{ + switch (gdk_cap) { + case GDK_CAP_BUTT: + case GDK_CAP_NOT_LAST: + return ART_PATH_STROKE_CAP_BUTT; + + case GDK_CAP_ROUND: + return ART_PATH_STROKE_CAP_ROUND; + + case GDK_CAP_PROJECTING: + return ART_PATH_STROKE_CAP_SQUARE; + + default: + g_assert_not_reached (); + return ART_PATH_STROKE_CAP_BUTT; /* shut up the compiler */ + } +} diff --git a/libgnomecanvas/gnome-canvas-util.h b/libgnomecanvas/gnome-canvas-util.h new file mode 100644 index 0000000000..cfd32c336e --- /dev/null +++ b/libgnomecanvas/gnome-canvas-util.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Miscellaneous utility functions for the GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#ifndef GNOME_CANVAS_UTIL_H +#define GNOME_CANVAS_UTIL_H + +#include <libgnomecanvas/gnome-canvas.h> + +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp_vpath_stroke.h> + + +G_BEGIN_DECLS + +typedef struct _GnomeCanvasPoints GnomeCanvasPoints; + +/* This structure defines an array of points. X coordinates are stored in the even-numbered + * indices, and Y coordinates are stored in the odd-numbered indices. num_points indicates the + * number of points, so the array is 2*num_points elements big. + */ +struct _GnomeCanvasPoints { + double *coords; + int num_points; + int ref_count; +}; + + +/* Allocate a new GnomeCanvasPoints structure with enough space for the specified number of points */ +GnomeCanvasPoints *gnome_canvas_points_new (int num_points); + +/* Increate ref count */ +GnomeCanvasPoints *gnome_canvas_points_ref (GnomeCanvasPoints *points); +#define gnome_canvas_points_unref gnome_canvas_points_free + +/* Decrease ref count and free structure if it has reached zero */ +void gnome_canvas_points_free (GnomeCanvasPoints *points); + +/* Given three points forming an angle, compute the coordinates of the inside and outside points of + * the mitered corner formed by a line of a given width at that angle. + * + * If the angle is less than 11 degrees, then FALSE is returned and the return points are not + * modified. Otherwise, TRUE is returned. + */ +int gnome_canvas_get_miter_points (double x1, double y1, double x2, double y2, double x3, double y3, + double width, + double *mx1, double *my1, double *mx2, double *my2); + +/* Compute the butt points of a line segment. If project is FALSE, then the results are as follows: + * + * -------------------* (bx1, by1) + * | + * (x1, y1) *------------------* (x2, y2) + * | + * -------------------* (bx2, by2) + * + * that is, the line is not projected beyond (x2, y2). If project is TRUE, then the results are as + * follows: + * + * -------------------* (bx1, by1) + * (x2, y2) | + * (x1, y1) *-------------* | + * | + * -------------------* (bx2, by2) + */ +void gnome_canvas_get_butt_points (double x1, double y1, double x2, double y2, + double width, int project, + double *bx1, double *by1, double *bx2, double *by2); + +/* Calculate the distance from a polygon to a point. The polygon's X coordinates are in the even + * indices of the poly array, and the Y coordinates are in the odd indices. + */ +double gnome_canvas_polygon_to_point (double *poly, int num_points, double x, double y); + + +/* Render the svp over the buf. */ +void gnome_canvas_render_svp (GnomeCanvasBuf *buf, ArtSVP *svp, guint32 rgba); + +/* Sets the svp to the new value, requesting repaint on what's changed. This function takes responsibility for + * freeing new_svp. + */ +void gnome_canvas_update_svp (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp); + +/* Sets the svp to the new value, clipping if necessary, and requesting repaint + * on what's changed. This function takes responsibility for freeing new_svp. + */ +void gnome_canvas_update_svp_clip (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp, + ArtSVP *clip_svp); + +/* Sets the svp to the new value, requesting repaint on what's changed. This + * function takes responsibility for freeing new_svp. This routine also adds the + * svp's bbox to the item's. + */ +void gnome_canvas_item_reset_bounds (GnomeCanvasItem *item); + +/* Sets the svp to the new value, requesting repaint on what's changed. This function takes responsibility for + * freeing new_svp. This routine also adds the svp's bbox to the item's. + */ +void gnome_canvas_item_update_svp (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp); + +/* Sets the svp to the new value, clipping if necessary, and requesting repaint + * on what's changed. This function takes responsibility for freeing new_svp. + */ +void gnome_canvas_item_update_svp_clip (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp, + ArtSVP *clip_svp); + +/* Request redraw of the svp if in aa mode, or the entire item in in xlib + * mode. + */ +void gnome_canvas_item_request_redraw_svp (GnomeCanvasItem *item, const ArtSVP *svp); + +/* Sets the bbox to the new value, requesting full repaint. */ +void gnome_canvas_update_bbox (GnomeCanvasItem *item, int x1, int y1, int x2, int y2); + +/* Ensure that the buffer is in RGB format, suitable for compositing. */ +void gnome_canvas_buf_ensure_buf (GnomeCanvasBuf *buf); + +/* Convert from GDK line join specifier to libart. */ +ArtPathStrokeJoinType gnome_canvas_join_gdk_to_art (GdkJoinStyle gdk_join); + +/* Convert from GDK line cap specifier to libart. */ +ArtPathStrokeCapType gnome_canvas_cap_gdk_to_art (GdkCapStyle gdk_cap); + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas-widget.c b/libgnomecanvas/gnome-canvas-widget.c new file mode 100644 index 0000000000..d94942c8a5 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-widget.c @@ -0,0 +1,599 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Widget item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#include <config.h> +#include <math.h> +#include <gtk/gtksignal.h> +#include "gnome-canvas-widget.h" + +enum { + PROP_0, + PROP_WIDGET, + PROP_X, + PROP_Y, + PROP_WIDTH, + PROP_HEIGHT, + PROP_ANCHOR, + PROP_SIZE_PIXELS +}; + + +static void gnome_canvas_widget_class_init (GnomeCanvasWidgetClass *class); +static void gnome_canvas_widget_init (GnomeCanvasWidget *witem); +static void gnome_canvas_widget_destroy (GtkObject *object); +static void gnome_canvas_widget_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void gnome_canvas_widget_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_widget_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static double gnome_canvas_widget_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item); +static void gnome_canvas_widget_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); + +static void gnome_canvas_widget_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf); +static void gnome_canvas_widget_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, + int width, int height); + +static GnomeCanvasItemClass *parent_class; + + +GType +gnome_canvas_widget_get_type (void) +{ + static GType widget_type; + + if (!widget_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasWidgetClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_widget_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasWidget), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_widget_init, + NULL /* value_table */ + }; + + widget_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasWidget", + &object_info, 0); + } + + return widget_type; +} + +static void +gnome_canvas_widget_class_init (GnomeCanvasWidgetClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_widget_set_property; + gobject_class->get_property = gnome_canvas_widget_get_property; + + g_object_class_install_property + (gobject_class, + PROP_WIDGET, + g_param_spec_object ("widget", NULL, NULL, + GTK_TYPE_WIDGET, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_X, + g_param_spec_double ("x", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_Y, + g_param_spec_double ("y", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_WIDTH, + g_param_spec_double ("width", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_HEIGHT, + g_param_spec_double ("height", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_ANCHOR, + g_param_spec_enum ("anchor", NULL, NULL, + GTK_TYPE_ANCHOR_TYPE, + GTK_ANCHOR_NW, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, + PROP_SIZE_PIXELS, + g_param_spec_boolean ("size_pixels", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_widget_destroy; + + item_class->update = gnome_canvas_widget_update; + item_class->point = gnome_canvas_widget_point; + item_class->bounds = gnome_canvas_widget_bounds; + item_class->render = gnome_canvas_widget_render; + item_class->draw = gnome_canvas_widget_draw; +} + +static void +gnome_canvas_widget_init (GnomeCanvasWidget *witem) +{ + witem->x = 0.0; + witem->y = 0.0; + witem->width = 0.0; + witem->height = 0.0; + witem->anchor = GTK_ANCHOR_NW; + witem->size_pixels = FALSE; +} + +static void +gnome_canvas_widget_destroy (GtkObject *object) +{ + GnomeCanvasWidget *witem; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_WIDGET (object)); + + witem = GNOME_CANVAS_WIDGET (object); + + if (witem->widget && !witem->in_destroy) { + g_signal_handler_disconnect (witem->widget, witem->destroy_id); + gtk_widget_destroy (witem->widget); + witem->widget = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +recalc_bounds (GnomeCanvasWidget *witem) +{ + GnomeCanvasItem *item; + double wx, wy; + + item = GNOME_CANVAS_ITEM (witem); + + /* Get world coordinates */ + + wx = witem->x; + wy = witem->y; + gnome_canvas_item_i2w (item, &wx, &wy); + + /* Get canvas pixel coordinates */ + + gnome_canvas_w2c (item->canvas, wx, wy, &witem->cx, &witem->cy); + + /* Anchor widget item */ + + switch (witem->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + witem->cx -= witem->cwidth / 2; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + witem->cx -= witem->cwidth; + break; + + default: + break; + } + + switch (witem->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + witem->cy -= witem->cheight / 2; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + witem->cy -= witem->cheight; + break; + + default: + break; + } + + /* Bounds */ + + item->x1 = witem->cx; + item->y1 = witem->cy; + item->x2 = witem->cx + witem->cwidth; + item->y2 = witem->cy + witem->cheight; + + if (witem->widget) + gtk_layout_move (GTK_LAYOUT (item->canvas), witem->widget, + witem->cx + item->canvas->zoom_xofs, + witem->cy + item->canvas->zoom_yofs); +} + +static void +do_destroy (GtkObject *object, gpointer data) +{ + GnomeCanvasWidget *witem; + + witem = data; + + witem->in_destroy = TRUE; + + gtk_object_destroy (data); +} + +static void +gnome_canvas_widget_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + GnomeCanvasWidget *witem; + GObject *obj; + int update; + int calc_bounds; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_WIDGET (object)); + + item = GNOME_CANVAS_ITEM (object); + witem = GNOME_CANVAS_WIDGET (object); + + update = FALSE; + calc_bounds = FALSE; + + switch (param_id) { + case PROP_WIDGET: + if (witem->widget) { + g_signal_handler_disconnect (witem->widget, witem->destroy_id); + gtk_container_remove (GTK_CONTAINER (item->canvas), witem->widget); + } + + obj = g_value_get_object (value); + if (obj) { + witem->widget = GTK_WIDGET (obj); + witem->destroy_id = g_signal_connect (obj, "destroy", + G_CALLBACK (do_destroy), + witem); + gtk_layout_put (GTK_LAYOUT (item->canvas), witem->widget, + witem->cx + item->canvas->zoom_xofs, + witem->cy + item->canvas->zoom_yofs); + } + + update = TRUE; + break; + + case PROP_X: + if (witem->x != g_value_get_double (value)) + { + witem->x = g_value_get_double (value); + calc_bounds = TRUE; + } + break; + + case PROP_Y: + if (witem->y != g_value_get_double (value)) + { + witem->y = g_value_get_double (value); + calc_bounds = TRUE; + } + break; + + case PROP_WIDTH: + if (witem->width != fabs (g_value_get_double (value))) + { + witem->width = fabs (g_value_get_double (value)); + update = TRUE; + } + break; + + case PROP_HEIGHT: + if (witem->height != fabs (g_value_get_double (value))) + { + witem->height = fabs (g_value_get_double (value)); + update = TRUE; + } + break; + + case PROP_ANCHOR: + if (witem->anchor != g_value_get_enum (value)) + { + witem->anchor = g_value_get_enum (value); + update = TRUE; + } + break; + + case PROP_SIZE_PIXELS: + if (witem->size_pixels != g_value_get_boolean (value)) + { + witem->size_pixels = g_value_get_boolean (value); + update = TRUE; + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } + + if (update) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->update) (item, NULL, NULL, 0); + + if (calc_bounds) + recalc_bounds (witem); +} + +static void +gnome_canvas_widget_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasWidget *witem; + + g_return_if_fail (object != NULL); + g_return_if_fail (GNOME_IS_CANVAS_WIDGET (object)); + + witem = GNOME_CANVAS_WIDGET (object); + + switch (param_id) { + case PROP_WIDGET: + g_value_set_object (value, (GObject *) witem->widget); + break; + + case PROP_X: + g_value_set_double (value, witem->x); + break; + + case PROP_Y: + g_value_set_double (value, witem->y); + break; + + case PROP_WIDTH: + g_value_set_double (value, witem->width); + break; + + case PROP_HEIGHT: + g_value_set_double (value, witem->height); + break; + + case PROP_ANCHOR: + g_value_set_enum (value, witem->anchor); + break; + + case PROP_SIZE_PIXELS: + g_value_set_boolean (value, witem->size_pixels); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +static void +gnome_canvas_widget_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasWidget *witem; + + witem = GNOME_CANVAS_WIDGET (item); + + if (parent_class->update) + (* parent_class->update) (item, affine, clip_path, flags); + + if (witem->widget) { + if (witem->size_pixels) { + witem->cwidth = (int) (witem->width + 0.5); + witem->cheight = (int) (witem->height + 0.5); + } else { + witem->cwidth = (int) (witem->width * item->canvas->pixels_per_unit + 0.5); + witem->cheight = (int) (witem->height * item->canvas->pixels_per_unit + 0.5); + } + + gtk_widget_set_size_request (witem->widget, witem->cwidth, witem->cheight); + } else { + witem->cwidth = 0.0; + witem->cheight = 0.0; + } + + recalc_bounds (witem); +} + +static void +gnome_canvas_widget_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf) +{ +#if 0 + GnomeCanvasWidget *witem; + + witem = GNOME_CANVAS_WIDGET (item); + + if (witem->widget) + gtk_widget_queue_draw (witem->widget); +#endif + +} + +static void +gnome_canvas_widget_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, + int width, int height) +{ +#if 0 + GnomeCanvasWidget *witem; + + witem = GNOME_CANVAS_WIDGET (item); + + if (witem->widget) + gtk_widget_queue_draw (witem->widget); +#endif +} + +static double +gnome_canvas_widget_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, GnomeCanvasItem **actual_item) +{ + GnomeCanvasWidget *witem; + double x1, y1, x2, y2; + double dx, dy; + + witem = GNOME_CANVAS_WIDGET (item); + + *actual_item = item; + + gnome_canvas_c2w (item->canvas, witem->cx, witem->cy, &x1, &y1); + + x2 = x1 + (witem->cwidth - 1) / item->canvas->pixels_per_unit; + y2 = y1 + (witem->cheight - 1) / item->canvas->pixels_per_unit; + + /* Is point inside widget bounds? */ + + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) + return 0.0; + + /* Point is outside widget bounds */ + + if (x < x1) + dx = x1 - x; + else if (x > x2) + dx = x - x2; + else + dx = 0.0; + + if (y < y1) + dy = y1 - y; + else if (y > y2) + dy = y - y2; + else + dy = 0.0; + + return sqrt (dx * dx + dy * dy); +} + +static void +gnome_canvas_widget_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasWidget *witem; + + witem = GNOME_CANVAS_WIDGET (item); + + *x1 = witem->x; + *y1 = witem->y; + + switch (witem->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_W: + case GTK_ANCHOR_SW: + break; + + case GTK_ANCHOR_N: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_S: + *x1 -= witem->width / 2.0; + break; + + case GTK_ANCHOR_NE: + case GTK_ANCHOR_E: + case GTK_ANCHOR_SE: + *x1 -= witem->width; + break; + + default: + break; + } + + switch (witem->anchor) { + case GTK_ANCHOR_NW: + case GTK_ANCHOR_N: + case GTK_ANCHOR_NE: + break; + + case GTK_ANCHOR_W: + case GTK_ANCHOR_CENTER: + case GTK_ANCHOR_E: + *y1 -= witem->height / 2.0; + break; + + case GTK_ANCHOR_SW: + case GTK_ANCHOR_S: + case GTK_ANCHOR_SE: + *y1 -= witem->height; + break; + + default: + break; + } + + *x2 = *x1 + witem->width; + *y2 = *y1 + witem->height; +} diff --git a/libgnomecanvas/gnome-canvas-widget.h b/libgnomecanvas/gnome-canvas-widget.h new file mode 100644 index 0000000000..f7517c3832 --- /dev/null +++ b/libgnomecanvas/gnome-canvas-widget.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* Widget item type for GnomeCanvas widget + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Author: Federico Mena <federico@nuclecu.unam.mx> + */ + +#ifndef GNOME_CANVAS_WIDGET_H +#define GNOME_CANVAS_WIDGET_H + + +#include <libgnomecanvas/gnome-canvas.h> + + +G_BEGIN_DECLS + + +/* Widget item for canvas. The widget is positioned with respect to an anchor point. + * The following object arguments are available: + * + * name type read/write description + * ------------------------------------------------------------------------------------------ + * widget GtkWidget* RW Pointer to the widget + * x double RW X coordinate of anchor point + * y double RW Y coordinate of anchor point + * width double RW Width of widget (see below) + * height double RW Height of widget (see below) + * anchor GtkAnchorType RW Anchor side for widget + * size_pixels boolean RW Specifies whether the widget size + * is specified in pixels or canvas units. + * If it is in pixels, then the widget will not + * be scaled when the canvas zoom factor changes. + * Otherwise, it will be scaled. + */ + + +#define GNOME_TYPE_CANVAS_WIDGET (gnome_canvas_widget_get_type ()) +#define GNOME_CANVAS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_WIDGET, GnomeCanvasWidget)) +#define GNOME_CANVAS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_WIDGET, GnomeCanvasWidgetClass)) +#define GNOME_IS_CANVAS_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_WIDGET)) +#define GNOME_IS_CANVAS_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_WIDGET)) +#define GNOME_CANVAS_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_WIDGET, GnomeCanvasWidgetClass)) + + +typedef struct _GnomeCanvasWidget GnomeCanvasWidget; +typedef struct _GnomeCanvasWidgetClass GnomeCanvasWidgetClass; + +struct _GnomeCanvasWidget { + GnomeCanvasItem item; + + GtkWidget *widget; /* The child widget */ + + double x, y; /* Position at anchor */ + double width, height; /* Dimensions of widget */ + GtkAnchorType anchor; /* Anchor side for widget */ + + int cx, cy; /* Top-left canvas coordinates for widget */ + int cwidth, cheight; /* Size of widget in pixels */ + + guint destroy_id; /* Signal connection id for destruction of child widget */ + + guint size_pixels : 1; /* Is size specified in (unchanging) pixels or units (get scaled)? */ + guint in_destroy : 1; /* Is child widget being destroyed? */ +}; + +struct _GnomeCanvasWidgetClass { + GnomeCanvasItemClass parent_class; +}; + + +/* Standard Gtk function */ +GType gnome_canvas_widget_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/gnome-canvas.c b/libgnomecanvas/gnome-canvas.c new file mode 100644 index 0000000000..5345a59200 --- /dev/null +++ b/libgnomecanvas/gnome-canvas.c @@ -0,0 +1,4090 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* + * GnomeCanvas widget - Tk-like canvas widget for Gnome + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is + * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties. + * + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@gimp.org> + */ + +/* + * TO-DO list for the canvas: + * + * - Allow to specify whether GnomeCanvasImage sizes are in units or pixels (scale or don't scale). + * + * - Implement a flag for gnome_canvas_item_reparent() that tells the function to keep the item + * visually in the same place, that is, to keep it in the same place with respect to the canvas + * origin. + * + * - GC put functions for items. + * + * - Widget item (finish it). + * + * - GList *gnome_canvas_gimme_all_items_contained_in_this_area (GnomeCanvas *canvas, Rectangle area); + * + * - Retrofit all the primitive items with microtile support. + * + * - Curve support for line item. + * + * - Arc item (Havoc has it; to be integrated in GnomeCanvasEllipse). + * + * - Sane font handling API. + * + * - Get_arg methods for items: + * - How to fetch the outline width and know whether it is in pixels or units? + */ + +/* + * Raph's TODO list for the antialiased canvas integration: + * + * - ::point() method for text item not accurate when affine transformed. + * + * - Clip rectangle not implemented in aa renderer for text item. + * + * - Clip paths only partially implemented. + * + * - Add more image loading techniques to work around imlib deficiencies. + */ + +#include <config.h> + +#include <math.h> +#include <string.h> +#include <stdio.h> +#include <gdk/gdkprivate.h> +#include <gtk/gtk.h> +#include "gailcanvas.h" +#include "gnome-canvas.h" +#include "gnome-canvas-i18n.h" +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_rect_uta.h> +#include <libart_lgpl/art_uta_rect.h> +#include <libart_lgpl/art_uta_ops.h> + +#include "gnome-canvas-marshal.h" +#include "gnome-canvas-marshal.c" + + +/* We must run our idle update handler *before* GDK wants to redraw. */ +#define CANVAS_IDLE_PRIORITY (GDK_PRIORITY_REDRAW - 5) + + +static void gnome_canvas_request_update (GnomeCanvas *canvas); +static void group_add (GnomeCanvasGroup *group, + GnomeCanvasItem *item); +static void group_remove (GnomeCanvasGroup *group, + GnomeCanvasItem *item); +static void add_idle (GnomeCanvas *canvas); + + +/*** GnomeCanvasItem ***/ + +/* Some convenience stuff */ +#define GCI_UPDATE_MASK (GNOME_CANVAS_UPDATE_REQUESTED | GNOME_CANVAS_UPDATE_AFFINE | GNOME_CANVAS_UPDATE_CLIP | GNOME_CANVAS_UPDATE_VISIBILITY) +#define GCI_EPSILON 1e-18 +#define GCI_PRINT_MATRIX(s,a) g_print ("%s %g %g %g %g %g %g\n", s, (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]) + +enum { + ITEM_PROP_0, + ITEM_PROP_PARENT +}; + +enum { + ITEM_EVENT, + ITEM_LAST_SIGNAL +}; + +static void gnome_canvas_item_class_init (GnomeCanvasItemClass *class); +static void gnome_canvas_item_init (GnomeCanvasItem *item); +static int emit_event (GnomeCanvas *canvas, GdkEvent *event); + +static guint item_signals[ITEM_LAST_SIGNAL]; + +static GtkObjectClass *item_parent_class; + + +/** + * gnome_canvas_item_get_type: + * + * Registers the &GnomeCanvasItem class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GnomeCanvasItem class. + **/ +GType +gnome_canvas_item_get_type (void) +{ + static GType canvas_item_type; + + if (!canvas_item_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasItemClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_item_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasItem), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_item_init, + NULL /* value_table */ + }; + + canvas_item_type = g_type_register_static (GTK_TYPE_OBJECT, "GnomeCanvasItem", + &object_info, 0); + } + + return canvas_item_type; +} + +/* Object initialization function for GnomeCanvasItem */ +static void +gnome_canvas_item_init (GnomeCanvasItem *item) +{ + item->object.flags |= GNOME_CANVAS_ITEM_VISIBLE; +} + +/** + * gnome_canvas_item_new: + * @parent: The parent group for the new item. + * @type: The object type of the item. + * @first_arg_name: A list of object argument name/value pairs, NULL-terminated, + * used to configure the item. For example, "fill_color", "black", + * "width_units", 5.0, NULL. + * @Varargs: + * + * Creates a new canvas item with @parent as its parent group. The item is + * created at the top of its parent's stack, and starts up as visible. The item + * is of the specified @type, for example, it can be + * gnome_canvas_rect_get_type(). The list of object arguments/value pairs is + * used to configure the item. If you need to pass construct time parameters, you + * should use g_object_new() to pass the parameters and + * gnome_canvas_item_construct() to set up the canvas item. + * + * Return value: The newly-created item. + **/ +GnomeCanvasItem * +gnome_canvas_item_new (GnomeCanvasGroup *parent, GType type, const gchar *first_arg_name, ...) +{ + GnomeCanvasItem *item; + va_list args; + + g_return_val_if_fail (GNOME_IS_CANVAS_GROUP (parent), NULL); + g_return_val_if_fail (g_type_is_a (type, gnome_canvas_item_get_type ()), NULL); + + item = GNOME_CANVAS_ITEM (g_object_new (type, NULL)); + + va_start (args, first_arg_name); + gnome_canvas_item_construct (item, parent, first_arg_name, args); + va_end (args); + + return item; +} + + +/* Performs post-creation operations on a canvas item (adding it to its parent + * group, etc.) + */ +static void +item_post_create_setup (GnomeCanvasItem *item) +{ + group_add (GNOME_CANVAS_GROUP (item->parent), item); + + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2 + 1, item->y2 + 1); + item->canvas->need_repick = TRUE; +} + +/* Set_property handler for canvas items */ +static void +gnome_canvas_item_set_property (GObject *gobject, guint param_id, + const GValue *value, GParamSpec *pspec) +{ + GnomeCanvasItem *item; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (gobject)); + + item = GNOME_CANVAS_ITEM (gobject); + + switch (param_id) { + case ITEM_PROP_PARENT: + if (item->parent != NULL) { + g_warning ("Cannot set `parent' argument after item has " + "already been constructed."); + } else if (g_value_get_object (value)) { + item->parent = GNOME_CANVAS_ITEM (g_value_get_object (value)); + item->canvas = item->parent->canvas; + item_post_create_setup (item); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + break; + } +} + +/* Get_property handler for canvas items */ +static void +gnome_canvas_item_get_property (GObject *gobject, guint param_id, + GValue *value, GParamSpec *pspec) +{ + GnomeCanvasItem *item; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (gobject)); + + item = GNOME_CANVAS_ITEM (gobject); + + switch (param_id) { + case ITEM_PROP_PARENT: + g_value_set_object (value, item->parent); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + break; + } +} + +/** + * gnome_canvas_item_construct: + * @item: An unconstructed canvas item. + * @parent: The parent group for the item. + * @first_arg_name: The name of the first argument for configuring the item. + * @args: The list of arguments used to configure the item. + * + * Constructs a canvas item; meant for use only by item implementations. + **/ +void +gnome_canvas_item_construct (GnomeCanvasItem *item, GnomeCanvasGroup *parent, + const gchar *first_arg_name, va_list args) +{ + g_return_if_fail (GNOME_IS_CANVAS_GROUP (parent)); + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + item->parent = GNOME_CANVAS_ITEM (parent); + item->canvas = item->parent->canvas; + + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + + item_post_create_setup (item); +} + + +/* If the item is visible, requests a redraw of it. */ +static void +redraw_if_visible (GnomeCanvasItem *item) +{ + if (item->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2 + 1, item->y2 + 1); +} + +/* Standard object dispose function for canvas items */ +static void +gnome_canvas_item_dispose (GObject *object) +{ + GnomeCanvasItem *item; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (object)); + + item = GNOME_CANVAS_ITEM (object); + + if (item->canvas) + redraw_if_visible (item); + + /* Make the canvas forget about us */ + + if (item->canvas && item == item->canvas->current_item) { + item->canvas->current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item->canvas && item == item->canvas->new_current_item) { + item->canvas->new_current_item = NULL; + item->canvas->need_repick = TRUE; + } + + if (item->canvas && item == item->canvas->grabbed_item) { + item->canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + if (item->canvas && item == item->canvas->focused_item) + item->canvas->focused_item = NULL; + + /* Normal destroy stuff */ + + if (item->object.flags & GNOME_CANVAS_ITEM_MAPPED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->unmap) (item); + + if (item->object.flags & GNOME_CANVAS_ITEM_REALIZED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->unrealize) (item); + + if (item->parent) + group_remove (GNOME_CANVAS_GROUP (item->parent), item); + + g_free (item->xform); + item->xform = NULL; + + G_OBJECT_CLASS (item_parent_class)->dispose (object); + /* items should remove any reference to item->canvas after the + first ::destroy */ + item->canvas = NULL; +} + +/* Realize handler for canvas items */ +static void +gnome_canvas_item_realize (GnomeCanvasItem *item) +{ + GTK_OBJECT_SET_FLAGS (item, GNOME_CANVAS_ITEM_REALIZED); + + gnome_canvas_item_request_update (item); +} + +/* Unrealize handler for canvas items */ +static void +gnome_canvas_item_unrealize (GnomeCanvasItem *item) +{ + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_REALIZED); +} + +/* Map handler for canvas items */ +static void +gnome_canvas_item_map (GnomeCanvasItem *item) +{ + GTK_OBJECT_SET_FLAGS (item, GNOME_CANVAS_ITEM_MAPPED); +} + +/* Unmap handler for canvas items */ +static void +gnome_canvas_item_unmap (GnomeCanvasItem *item) +{ + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_MAPPED); +} + +/* Update handler for canvas items */ +static void +gnome_canvas_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_UPDATE); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_AFFINE); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_CLIP); + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_NEED_VIS); +} + +#define noHACKISH_AFFINE + +/* + * This routine invokes the update method of the item + * Please notice, that we take parent to canvas pixel matrix as argument + * unlike virtual method ::update, whose argument is item 2 canvas pixel + * matrix + * + * I will try to force somewhat meaningful naming for affines (Lauris) + * General naming rule is FROM2TO, where FROM and TO are abbreviations + * So p2cpx is Parent2CanvasPixel and i2cpx is Item2CanvasPixel + * I hope that this helps to keep track of what really happens + * + */ + +static void +gnome_canvas_item_invoke_update (GnomeCanvasItem *item, double *p2cpx, ArtSVP *clip_path, int flags) +{ + int child_flags; + gdouble i2cpx[6]; + +#ifdef HACKISH_AFFINE + double i2w[6], w2c[6], i2c[6]; +#endif + + child_flags = flags; + if (!(item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) + child_flags &= ~GNOME_CANVAS_UPDATE_IS_VISIBLE; + + /* Calculate actual item transformation matrix */ + + if (item->xform) { + if (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL) { + /* Item has full affine */ + art_affine_multiply (i2cpx, item->xform, p2cpx); + } else { + /* Item has only translation */ + memcpy (i2cpx, p2cpx, 4 * sizeof (gdouble)); + i2cpx[4] = item->xform[0] * p2cpx[0] + item->xform[1] * p2cpx[2] + p2cpx[4]; + i2cpx[5] = item->xform[0] * p2cpx[1] + item->xform[1] * p2cpx[3] + p2cpx[5]; + } + } else { + /* Item has no matrix (i.e. identity) */ + memcpy (i2cpx, p2cpx, 6 * sizeof (gdouble)); + } + +#ifdef HACKISH_AFFINE + gnome_canvas_item_i2w_affine (item, i2w); + gnome_canvas_w2c_affine (item->canvas, w2c); + art_affine_multiply (i2c, i2w, w2c); + /* invariant (doesn't hold now): child_affine == i2c */ + child_affine = i2c; +#endif + + /* apply object flags to child flags */ + + child_flags &= ~GNOME_CANVAS_UPDATE_REQUESTED; + + if (item->object.flags & GNOME_CANVAS_ITEM_NEED_UPDATE) + child_flags |= GNOME_CANVAS_UPDATE_REQUESTED; + + if (item->object.flags & GNOME_CANVAS_ITEM_NEED_AFFINE) + child_flags |= GNOME_CANVAS_UPDATE_AFFINE; + + if (item->object.flags & GNOME_CANVAS_ITEM_NEED_CLIP) + child_flags |= GNOME_CANVAS_UPDATE_CLIP; + + if (item->object.flags & GNOME_CANVAS_ITEM_NEED_VIS) + child_flags |= GNOME_CANVAS_UPDATE_VISIBILITY; + + if (child_flags & GCI_UPDATE_MASK) { + if (GNOME_CANVAS_ITEM_GET_CLASS (item)->update) + GNOME_CANVAS_ITEM_GET_CLASS (item)->update (item, i2cpx, clip_path, child_flags); + } +} + +/* + * This routine invokes the point method of the item. + * The arguments x, y should be in the parent item local coordinates. + * + * This is potentially evil, as we are relying on matrix inversion (Lauris) + */ + +static double +gnome_canvas_item_invoke_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) +{ + /* Calculate x & y in item local coordinates */ + + if (item->xform) { + if (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL) { + gdouble p2i[6], t; + /* Item has full affine */ + art_affine_invert (p2i, item->xform); + t = x * p2i[0] + y * p2i[2] + p2i[4]; + y = x * p2i[1] + y * p2i[3] + p2i[5]; + x = t; + } else { + /* Item has only translation */ + x -= item->xform[0]; + y -= item->xform[1]; + } + } + +#ifdef HACKISH_AFFINE + double i2w[6], w2c[6], i2c[6], c2i[6]; + ArtPoint c, i; +#endif + +#ifdef HACKISH_AFFINE + gnome_canvas_item_i2w_affine (item, i2w); + gnome_canvas_w2c_affine (item->canvas, w2c); + art_affine_multiply (i2c, i2w, w2c); + art_affine_invert (c2i, i2c); + c.x = cx; + c.y = cy; + art_affine_point (&i, &c, c2i); + x = i.x; + y = i.y; +#endif + + if (GNOME_CANVAS_ITEM_GET_CLASS (item)->point) + return GNOME_CANVAS_ITEM_GET_CLASS (item)->point (item, x, y, cx, cy, actual_item); + + return 1e18; +} + +/** + * gnome_canvas_item_set: + * @item: A canvas item. + * @first_arg_name: The list of object argument name/value pairs used to configure the item. + * @Varargs: + * + * Configures a canvas item. The arguments in the item are set to the specified + * values, and the item is repainted as appropriate. + **/ +void +gnome_canvas_item_set (GnomeCanvasItem *item, const gchar *first_arg_name, ...) +{ + va_list args; + + va_start (args, first_arg_name); + gnome_canvas_item_set_valist (item, first_arg_name, args); + va_end (args); +} + + +/** + * gnome_canvas_item_set_valist: + * @item: A canvas item. + * @first_arg_name: The name of the first argument used to configure the item. + * @args: The list of object argument name/value pairs used to configure the item. + * + * Configures a canvas item. The arguments in the item are set to the specified + * values, and the item is repainted as appropriate. + **/ +void +gnome_canvas_item_set_valist (GnomeCanvasItem *item, const gchar *first_arg_name, va_list args) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + g_object_set_valist (G_OBJECT (item), first_arg_name, args); + +#if 0 + /* I commented this out, because item implementations have to schedule update/redraw */ + redraw_if_visible (item); +#endif + + item->canvas->need_repick = TRUE; +} + + +/** + * gnome_canvas_item_affine_relative: + * @item: A canvas item. + * @affine: An affine transformation matrix. + * + * Combines the specified affine transformation matrix with the item's current + * transformation. NULL affine is not allowed. + **/ +#define GCIAR_EPSILON 1e-6 +void +gnome_canvas_item_affine_relative (GnomeCanvasItem *item, const double affine[6]) +{ + gdouble i2p[6]; + + g_return_if_fail (item != NULL); + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (affine != NULL); + + /* Calculate actual item transformation matrix */ + + if (item->xform) { + if (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL) { + /* Item has full affine */ + art_affine_multiply (i2p, affine, item->xform); + } else { + /* Item has only translation */ + memcpy (i2p, affine, 6 * sizeof (gdouble)); + i2p[4] += item->xform[0]; + i2p[5] += item->xform[1]; + } + } else { + /* Item has no matrix (i.e. identity) */ + memcpy (i2p, affine, 6 * sizeof (gdouble)); + } + + gnome_canvas_item_affine_absolute (item, i2p); +} + +/** + * gnome_canvas_item_affine_absolute: + * @item: A canvas item. + * @affine: An affine transformation matrix. + * + * Makes the item's affine transformation matrix be equal to the specified + * matrix. NULL affine is treated as identity. + **/ +void +gnome_canvas_item_affine_absolute (GnomeCanvasItem *item, const double i2p[6]) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (i2p && + (fabs (i2p[0] - 1.0) < GCI_EPSILON) && + (fabs (i2p[1] - 0.0) < GCI_EPSILON) && + (fabs (i2p[2] - 0.0) < GCI_EPSILON) && + (fabs (i2p[3] - 1.0) < GCI_EPSILON) && + (fabs (i2p[4] - 0.0) < GCI_EPSILON) && + (fabs (i2p[5] - 0.0) < GCI_EPSILON)) { + /* We are identity */ + i2p = NULL; + } + + if (i2p) { + if (item->xform && !(item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL)) { + /* We do not want to deal with translation-only affines */ + g_free (item->xform); + item->xform = NULL; + } + if (!item->xform) item->xform = g_new (gdouble, 6); + memcpy (item->xform, i2p, 6 * sizeof (gdouble)); + item->object.flags |= GNOME_CANVAS_ITEM_AFFINE_FULL; + } else { + if (item->xform) { + g_free (item->xform); + item->xform = NULL; + } + } + + if (!(item->object.flags & GNOME_CANVAS_ITEM_NEED_AFFINE)) { + /* Request update */ + item->object.flags |= GNOME_CANVAS_ITEM_NEED_AFFINE; + gnome_canvas_item_request_update (item); + } + + item->canvas->need_repick = TRUE; +} + + +/** + * gnome_canvas_item_move: + * @item: A canvas item. + * @dx: Horizontal offset. + * @dy: Vertical offset. + * + * Moves a canvas item by creating an affine transformation matrix for + * translation by using the specified values. This happens in item + * local coordinate system, so if you have nontrivial transform, it + * most probably does not do, what you want. + **/ +void +gnome_canvas_item_move (GnomeCanvasItem *item, double dx, double dy) +{ + double translate[6]; + + g_return_if_fail (item != NULL); + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + art_affine_translate (translate, dx, dy); + + gnome_canvas_item_affine_relative (item, translate); +} + +/* Convenience function to reorder items in a group's child list. This puts the + * specified link after the "before" link. Returns TRUE if the list was changed. + */ +static gboolean +put_item_after (GList *link, GList *before) +{ + GnomeCanvasGroup *parent; + GList *old_before, *old_after; + GList *after; + + parent = GNOME_CANVAS_GROUP (GNOME_CANVAS_ITEM (link->data)->parent); + + if (before) + after = before->next; + else + after = parent->item_list; + + if (before == link || after == link) + return FALSE; + + /* Unlink */ + + old_before = link->prev; + old_after = link->next; + + if (old_before) + old_before->next = old_after; + else + parent->item_list = old_after; + + if (old_after) + old_after->prev = old_before; + else + parent->item_list_end = old_before; + + /* Relink */ + + link->prev = before; + if (before) + before->next = link; + else + parent->item_list = link; + + link->next = after; + if (after) + after->prev = link; + else + parent->item_list_end = link; + + return TRUE; +} + + +/** + * gnome_canvas_item_raise: + * @item: A canvas item. + * @positions: Number of steps to raise the item. + * + * Raises the item in its parent's stack by the specified number of positions. + * If the number of positions is greater than the distance to the top of the + * stack, then the item is put at the top. + **/ +void +gnome_canvas_item_raise (GnomeCanvasItem *item, int positions) +{ + GList *link, *before; + GnomeCanvasGroup *parent; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 0); + + if (!item->parent || positions == 0) + return; + + parent = GNOME_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + for (before = link; positions && before; positions--) + before = before->next; + + if (!before) + before = parent->item_list_end; + + if (put_item_after (link, before)) { + redraw_if_visible (item); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_lower: + * @item: A canvas item. + * @positions: Number of steps to lower the item. + * + * Lowers the item in its parent's stack by the specified number of positions. + * If the number of positions is greater than the distance to the bottom of the + * stack, then the item is put at the bottom. + **/ +void +gnome_canvas_item_lower (GnomeCanvasItem *item, int positions) +{ + GList *link, *before; + GnomeCanvasGroup *parent; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (positions >= 1); + + if (!item->parent || positions == 0) + return; + + parent = GNOME_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (link->prev) + for (before = link->prev; positions && before; positions--) + before = before->prev; + else + before = NULL; + + if (put_item_after (link, before)) { + redraw_if_visible (item); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_raise_to_top: + * @item: A canvas item. + * + * Raises an item to the top of its parent's stack. + **/ +void +gnome_canvas_item_raise_to_top (GnomeCanvasItem *item) +{ + GList *link; + GnomeCanvasGroup *parent; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (!item->parent) + return; + + parent = GNOME_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (put_item_after (link, parent->item_list_end)) { + redraw_if_visible (item); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_lower_to_bottom: + * @item: A canvas item. + * + * Lowers an item to the bottom of its parent's stack. + **/ +void +gnome_canvas_item_lower_to_bottom (GnomeCanvasItem *item) +{ + GList *link; + GnomeCanvasGroup *parent; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (!item->parent) + return; + + parent = GNOME_CANVAS_GROUP (item->parent); + link = g_list_find (parent->item_list, item); + g_assert (link != NULL); + + if (put_item_after (link, NULL)) { + redraw_if_visible (item); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_show: + * @item: A canvas item. + * + * Shows a canvas item. If the item was already shown, then no action is taken. + **/ +void +gnome_canvas_item_show (GnomeCanvasItem *item) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (!(item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) { + item->object.flags |= GNOME_CANVAS_ITEM_VISIBLE; + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2 + 1, item->y2 + 1); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_hide: + * @item: A canvas item. + * + * Hides a canvas item. If the item was already hidden, then no action is + * taken. + **/ +void +gnome_canvas_item_hide (GnomeCanvasItem *item) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (item->object.flags & GNOME_CANVAS_ITEM_VISIBLE) { + item->object.flags &= ~GNOME_CANVAS_ITEM_VISIBLE; + gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2 + 1, item->y2 + 1); + item->canvas->need_repick = TRUE; + } +} + + +/** + * gnome_canvas_item_grab: + * @item: A canvas item. + * @event_mask: Mask of events that will be sent to this item. + * @cursor: If non-NULL, the cursor that will be used while the grab is active. + * @etime: The timestamp required for grabbing the mouse, or GDK_CURRENT_TIME. + * + * Specifies that all events that match the specified event mask should be sent + * to the specified item, and also grabs the mouse by calling + * gdk_pointer_grab(). The event mask is also used when grabbing the pointer. + * If @cursor is not NULL, then that cursor is used while the grab is active. + * The @etime parameter is the timestamp required for grabbing the mouse. + * + * Return value: If an item was already grabbed, it returns %GDK_GRAB_ALREADY_GRABBED. If + * the specified item was hidden by calling gnome_canvas_item_hide(), then it + * returns %GDK_GRAB_NOT_VIEWABLE. Else, it returns the result of calling + * gdk_pointer_grab(). + **/ +int +gnome_canvas_item_grab (GnomeCanvasItem *item, guint event_mask, GdkCursor *cursor, guint32 etime) +{ + int retval; + + g_return_val_if_fail (GNOME_IS_CANVAS_ITEM (item), GDK_GRAB_NOT_VIEWABLE); + g_return_val_if_fail (gtk_widget_get_mapped (GTK_WIDGET (item->canvas)), GDK_GRAB_NOT_VIEWABLE); + + if (item->canvas->grabbed_item) + return GDK_GRAB_ALREADY_GRABBED; + + if (!(item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) + return GDK_GRAB_NOT_VIEWABLE; + + retval = gdk_pointer_grab (item->canvas->layout.bin_window, + FALSE, + event_mask, + NULL, + cursor, + etime); + + if (retval != GDK_GRAB_SUCCESS) + return retval; + + item->canvas->grabbed_item = item; + item->canvas->grabbed_event_mask = event_mask; + item->canvas->current_item = item; /* So that events go to the grabbed item */ + + return retval; +} + + +/** + * gnome_canvas_item_ungrab: + * @item: A canvas item that holds a grab. + * @etime: The timestamp for ungrabbing the mouse. + * + * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the + * mouse. + **/ +void +gnome_canvas_item_ungrab (GnomeCanvasItem *item, guint32 etime) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + if (item->canvas->grabbed_item != item) + return; + + item->canvas->grabbed_item = NULL; + + gdk_pointer_ungrab (etime); +} + + +/** + * gnome_canvas_item_i2w_affine: + * @item: A canvas item + * @affine: An affine transformation matrix (return value). + * + * Gets the affine transform that converts from the item's coordinate system to + * world coordinates. + **/ +void +gnome_canvas_item_i2w_affine (GnomeCanvasItem *item, double affine[6]) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (affine != NULL); + + art_affine_identity (affine); + + while (item) { + if (item->xform != NULL) { + if (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL) { + art_affine_multiply (affine, affine, item->xform); + } else { + affine[4] += item->xform[0]; + affine[5] += item->xform[1]; + } + } + + item = item->parent; + } +} + +/** + * gnome_canvas_item_w2i: + * @item: A canvas item. + * @x: X coordinate to convert (input/output value). + * @y: Y coordinate to convert (input/output value). + * + * Converts a coordinate pair from world coordinates to item-relative + * coordinates. + **/ +void +gnome_canvas_item_w2i (GnomeCanvasItem *item, double *x, double *y) +{ + double affine[6], inv[6]; + ArtPoint w, i; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + gnome_canvas_item_i2w_affine (item, affine); + art_affine_invert (inv, affine); + w.x = *x; + w.y = *y; + art_affine_point (&i, &w, inv); + *x = i.x; + *y = i.y; +} + + +/** + * gnome_canvas_item_i2w: + * @item: A canvas item. + * @x: X coordinate to convert (input/output value). + * @y: Y coordinate to convert (input/output value). + * + * Converts a coordinate pair from item-relative coordinates to world + * coordinates. + **/ +void +gnome_canvas_item_i2w (GnomeCanvasItem *item, double *x, double *y) +{ + double affine[6]; + ArtPoint w, i; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (x != NULL); + g_return_if_fail (y != NULL); + + gnome_canvas_item_i2w_affine (item, affine); + i.x = *x; + i.y = *y; + art_affine_point (&w, &i, affine); + *x = w.x; + *y = w.y; +} + +/** + * gnome_canvas_item_i2c_affine: + * @item: A canvas item. + * @affine: An affine transformation matrix (return value). + * + * Gets the affine transform that converts from item-relative coordinates to + * canvas pixel coordinates. + **/ +void +gnome_canvas_item_i2c_affine (GnomeCanvasItem *item, double affine[6]) +{ + double i2w[6], w2c[6]; + + gnome_canvas_item_i2w_affine (item, i2w); + gnome_canvas_w2c_affine (item->canvas, w2c); + art_affine_multiply (affine, i2w, w2c); +} + +/* Returns whether the item is an inferior of or is equal to the parent. */ +static int +is_descendant (GnomeCanvasItem *item, GnomeCanvasItem *parent) +{ + for (; item; item = item->parent) + if (item == parent) + return TRUE; + + return FALSE; +} + +/** + * gnome_canvas_item_reparent: + * @item: A canvas item. + * @new_group: A canvas group. + * + * Changes the parent of the specified item to be the new group. The item keeps + * its group-relative coordinates as for its old parent, so the item may change + * its absolute position within the canvas. + **/ +void +gnome_canvas_item_reparent (GnomeCanvasItem *item, GnomeCanvasGroup *new_group) +{ + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (GNOME_IS_CANVAS_GROUP (new_group)); + + /* Both items need to be in the same canvas */ + g_return_if_fail (item->canvas == GNOME_CANVAS_ITEM (new_group)->canvas); + + /* The group cannot be an inferior of the item or be the item itself -- + * this also takes care of the case where the item is the root item of + * the canvas. */ + g_return_if_fail (!is_descendant (GNOME_CANVAS_ITEM (new_group), item)); + + /* Everything is ok, now actually reparent the item */ + + g_object_ref (G_OBJECT (item)); /* protect it from the unref in group_remove */ + + redraw_if_visible (item); + + group_remove (GNOME_CANVAS_GROUP (item->parent), item); + item->parent = GNOME_CANVAS_ITEM (new_group); + group_add (new_group, item); + + /* Redraw and repick */ + + redraw_if_visible (item); + item->canvas->need_repick = TRUE; + + g_object_unref (G_OBJECT (item)); +} + +/** + * gnome_canvas_item_grab_focus: + * @item: A canvas item. + * + * Makes the specified item take the keyboard focus, so all keyboard events will + * be sent to it. If the canvas widget itself did not have the focus, it grabs + * it as well. + **/ +void +gnome_canvas_item_grab_focus (GnomeCanvasItem *item) +{ + GnomeCanvasItem *focused_item; + GdkEvent ev; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + g_return_if_fail (gtk_widget_get_can_focus (GTK_WIDGET (item->canvas))); + + focused_item = item->canvas->focused_item; + + if (focused_item) { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = GTK_LAYOUT (item->canvas)->bin_window; + ev.focus_change.send_event = FALSE; + ev.focus_change.in = FALSE; + + emit_event (item->canvas, &ev); + } + + item->canvas->focused_item = item; + gtk_widget_grab_focus (GTK_WIDGET (item->canvas)); + + if (focused_item) { + ev.focus_change.type = GDK_FOCUS_CHANGE; + ev.focus_change.window = GTK_LAYOUT (item->canvas)->bin_window; + ev.focus_change.send_event = FALSE; + ev.focus_change.in = TRUE; + + emit_event (item->canvas, &ev); + } +} + + +/** + * gnome_canvas_item_get_bounds: + * @item: A canvas item. + * @x1: Leftmost edge of the bounding box (return value). + * @y1: Upper edge of the bounding box (return value). + * @x2: Rightmost edge of the bounding box (return value). + * @y2: Lower edge of the bounding box (return value). + * + * Queries the bounding box of a canvas item. The bounds are returned in the + * coordinate system of the item's parent. + **/ +void +gnome_canvas_item_get_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + double tx1, ty1, tx2, ty2; + ArtPoint p1, p2, p3, p4; + ArtPoint q1, q2, q3, q4; + double min_x1, min_y1, min_x2, min_y2; + double max_x1, max_y1, max_x2, max_y2; + + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + tx1 = ty1 = tx2 = ty2 = 0.0; + + /* Get the item's bounds in its coordinate system */ + + if (GNOME_CANVAS_ITEM_GET_CLASS (item)->bounds) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->bounds) (item, &tx1, &ty1, &tx2, &ty2); + + /* Make the bounds relative to the item's parent coordinate system */ + + if (item->xform && (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL)) { + p1.x = p2.x = tx1; + p1.y = p4.y = ty1; + p3.x = p4.x = tx2; + p2.y = p3.y = ty2; + + art_affine_point (&q1, &p1, item->xform); + art_affine_point (&q2, &p2, item->xform); + art_affine_point (&q3, &p3, item->xform); + art_affine_point (&q4, &p4, item->xform); + + if (q1.x < q2.x) { + min_x1 = q1.x; + max_x1 = q2.x; + } else { + min_x1 = q2.x; + max_x1 = q1.x; + } + + if (q1.y < q2.y) { + min_y1 = q1.y; + max_y1 = q2.y; + } else { + min_y1 = q2.y; + max_y1 = q1.y; + } + + if (q3.x < q4.x) { + min_x2 = q3.x; + max_x2 = q4.x; + } else { + min_x2 = q4.x; + max_x2 = q3.x; + } + + if (q3.y < q4.y) { + min_y2 = q3.y; + max_y2 = q4.y; + } else { + min_y2 = q4.y; + max_y2 = q3.y; + } + + tx1 = MIN (min_x1, min_x2); + ty1 = MIN (min_y1, min_y2); + tx2 = MAX (max_x1, max_x2); + ty2 = MAX (max_y1, max_y2); + } else if (item->xform) { + tx1 += item->xform[0]; + ty1 += item->xform[1]; + tx2 += item->xform[0]; + ty2 += item->xform[1]; + } + + /* Return the values */ + + if (x1) + *x1 = tx1; + + if (y1) + *y1 = ty1; + + if (x2) + *x2 = tx2; + + if (y2) + *y2 = ty2; +} + + +/** + * gnome_canvas_item_request_update + * @item: A canvas item. + * + * To be used only by item implementations. Requests that the canvas queue an + * update for the specified item. + **/ +void +gnome_canvas_item_request_update (GnomeCanvasItem *item) +{ + if (item->object.flags & GNOME_CANVAS_ITEM_NEED_UPDATE) + return; + + item->object.flags |= GNOME_CANVAS_ITEM_NEED_UPDATE; + + if (item->parent != NULL) { + /* Recurse up the tree */ + gnome_canvas_item_request_update (item->parent); + } else { + /* Have reached the top of the tree, make sure the update call gets scheduled. */ + gnome_canvas_request_update (item->canvas); + } +} + +/*** GnomeCanvasGroup ***/ + + +enum { + GROUP_PROP_0, + GROUP_PROP_X, + GROUP_PROP_Y +}; + + +static void gnome_canvas_group_class_init (GnomeCanvasGroupClass *class); +static void gnome_canvas_group_init (GnomeCanvasGroup *group); +static void gnome_canvas_group_set_property(GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static void gnome_canvas_group_get_property(GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void gnome_canvas_group_destroy (GtkObject *object); + +static void gnome_canvas_group_update (GnomeCanvasItem *item, double *affine, + ArtSVP *clip_path, int flags); +static void gnome_canvas_group_realize (GnomeCanvasItem *item); +static void gnome_canvas_group_unrealize (GnomeCanvasItem *item); +static void gnome_canvas_group_map (GnomeCanvasItem *item); +static void gnome_canvas_group_unmap (GnomeCanvasItem *item); +static void gnome_canvas_group_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); +static double gnome_canvas_group_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item); +static void gnome_canvas_group_bounds (GnomeCanvasItem *item, double *x1, double *y1, + double *x2, double *y2); +static void gnome_canvas_group_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf); + + +static GnomeCanvasItemClass *group_parent_class; + + +/** + * gnome_canvas_group_get_type: + * + * Registers the &GnomeCanvasGroup class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GnomeCanvasGroup class. + **/ +GType +gnome_canvas_group_get_type (void) +{ + static GType canvas_group_type; + + if (!canvas_group_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasGroupClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_group_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvasGroup), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_group_init, + NULL /* value_table */ + }; + + canvas_group_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasGroup", + &object_info, 0); + } + + return canvas_group_type; +} + +/* Class initialization function for GnomeCanvasGroupClass */ +static void +gnome_canvas_group_class_init (GnomeCanvasGroupClass *class) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + gobject_class = (GObjectClass *) class; + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + group_parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_group_set_property; + gobject_class->get_property = gnome_canvas_group_get_property; + + g_object_class_install_property + (gobject_class, GROUP_PROP_X, + g_param_spec_double ("x", + _("X"), + _("X"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + g_object_class_install_property + (gobject_class, GROUP_PROP_Y, + g_param_spec_double ("y", + _("Y"), + _("Y"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + object_class->destroy = gnome_canvas_group_destroy; + + item_class->update = gnome_canvas_group_update; + item_class->realize = gnome_canvas_group_realize; + item_class->unrealize = gnome_canvas_group_unrealize; + item_class->map = gnome_canvas_group_map; + item_class->unmap = gnome_canvas_group_unmap; + item_class->draw = gnome_canvas_group_draw; + item_class->render = gnome_canvas_group_render; + item_class->point = gnome_canvas_group_point; + item_class->bounds = gnome_canvas_group_bounds; +} + +/* Object initialization function for GnomeCanvasGroup */ +static void +gnome_canvas_group_init (GnomeCanvasGroup *group) +{ +#if 0 + group->xpos = 0.0; + group->ypos = 0.0; +#endif +} + +/* Translate handler for canvas groups */ +static double * +gnome_canvas_ensure_translate (GnomeCanvasItem *item) +{ + if (item->xform == NULL) { + GTK_OBJECT_UNSET_FLAGS (item, GNOME_CANVAS_ITEM_AFFINE_FULL); + item->xform = g_new (double, 2); + item->xform[0] = 0.0; + item->xform[1] = 0.0; + return item->xform; + } else if (item->object.flags & GNOME_CANVAS_ITEM_AFFINE_FULL) { + return item->xform + 4; + } else { + return item->xform; + } +} + +/* Set_property handler for canvas groups */ +static void +gnome_canvas_group_set_property (GObject *gobject, guint param_id, + const GValue *value, GParamSpec *pspec) +{ + GnomeCanvasItem *item; + double *xlat; + + g_return_if_fail (GNOME_IS_CANVAS_GROUP (gobject)); + + item = GNOME_CANVAS_ITEM (gobject); + + switch (param_id) { + case GROUP_PROP_X: + xlat = gnome_canvas_ensure_translate (item); + xlat[0] = g_value_get_double (value); + break; + + case GROUP_PROP_Y: + xlat = gnome_canvas_ensure_translate (item); + xlat[1] = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + break; + } +} + +/* Get_property handler for canvas groups */ +static void +gnome_canvas_group_get_property (GObject *gobject, guint param_id, + GValue *value, GParamSpec *pspec) +{ + GnomeCanvasItem *item; + + g_return_if_fail (GNOME_IS_CANVAS_GROUP (gobject)); + + item = GNOME_CANVAS_ITEM (gobject); + + switch (param_id) { + case GROUP_PROP_X: + if (item->xform == NULL) + g_value_set_double (value, 0); + else if (GTK_OBJECT (gobject)->flags & GNOME_CANVAS_ITEM_AFFINE_FULL) + g_value_set_double (value, item->xform[4]); + else + g_value_set_double (value, item->xform[0]); + break; + + case GROUP_PROP_Y: + if (item->xform == NULL) + g_value_set_double (value, 0); + else if (GTK_OBJECT (gobject)->flags & GNOME_CANVAS_ITEM_AFFINE_FULL) + g_value_set_double (value, item->xform[5]); + else + g_value_set_double (value, item->xform[1]); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, param_id, pspec); + break; + } +} + +/* Destroy handler for canvas groups */ +static void +gnome_canvas_group_destroy (GtkObject *object) +{ + GnomeCanvasGroup *group; + + g_return_if_fail (GNOME_IS_CANVAS_GROUP (object)); + + group = GNOME_CANVAS_GROUP (object); + + while (group->item_list) { + // child is unref'ed by the child's group_remove(). + gtk_object_destroy (GTK_OBJECT (group->item_list->data)); + } + + if (GTK_OBJECT_CLASS (group_parent_class)->destroy) + (* GTK_OBJECT_CLASS (group_parent_class)->destroy) (object); +} + +/* Update handler for canvas groups */ +static void +gnome_canvas_group_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *i; + ArtDRect bbox, child_bbox; + + group = GNOME_CANVAS_GROUP (item); + + (* group_parent_class->update) (item, affine, clip_path, flags); + + bbox.x0 = 0; + bbox.y0 = 0; + bbox.x1 = 0; + bbox.y1 = 0; + + for (list = group->item_list; list; list = list->next) { + i = list->data; + + gnome_canvas_item_invoke_update (i, affine, clip_path, flags); + + child_bbox.x0 = i->x1; + child_bbox.y0 = i->y1; + child_bbox.x1 = i->x2; + child_bbox.y1 = i->y2; + art_drect_union (&bbox, &bbox, &child_bbox); + } + item->x1 = bbox.x0; + item->y1 = bbox.y0; + item->x2 = bbox.x1; + item->y2 = bbox.y1; +} + +/* Realize handler for canvas groups */ +static void +gnome_canvas_group_realize (GnomeCanvasItem *item) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *i; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + i = list->data; + + if (!(i->object.flags & GNOME_CANVAS_ITEM_REALIZED)) + (* GNOME_CANVAS_ITEM_GET_CLASS (i)->realize) (i); + } + + (* group_parent_class->realize) (item); +} + +/* Unrealize handler for canvas groups */ +static void +gnome_canvas_group_unrealize (GnomeCanvasItem *item) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *i; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + i = list->data; + + if (i->object.flags & GNOME_CANVAS_ITEM_REALIZED) + (* GNOME_CANVAS_ITEM_GET_CLASS (i)->unrealize) (i); + } + + (* group_parent_class->unrealize) (item); +} + +/* Map handler for canvas groups */ +static void +gnome_canvas_group_map (GnomeCanvasItem *item) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *i; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + i = list->data; + + if (!(i->object.flags & GNOME_CANVAS_ITEM_MAPPED)) + (* GNOME_CANVAS_ITEM_GET_CLASS (i)->map) (i); + } + + (* group_parent_class->map) (item); +} + +/* Unmap handler for canvas groups */ +static void +gnome_canvas_group_unmap (GnomeCanvasItem *item) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *i; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + i = list->data; + + if (i->object.flags & GNOME_CANVAS_ITEM_MAPPED) + (* GNOME_CANVAS_ITEM_GET_CLASS (i)->unmap) (i); + } + + (* group_parent_class->unmap) (item); +} + +/* Draw handler for canvas groups */ +static void +gnome_canvas_group_draw (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *child = NULL; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + child = list->data; + + if (((child->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + && ((child->x1 < (x + width)) + && (child->y1 < (y + height)) + && (child->x2 > x) + && (child->y2 > y))) + || ((GTK_OBJECT_FLAGS (child) & GNOME_CANVAS_ITEM_ALWAYS_REDRAW) + && (child->x1 < child->canvas->redraw_x2) + && (child->y1 < child->canvas->redraw_y2) + && (child->x2 > child->canvas->redraw_x1) + && (child->y2 > child->canvas->redraw_y2))) + if (GNOME_CANVAS_ITEM_GET_CLASS (child)->draw) + (* GNOME_CANVAS_ITEM_GET_CLASS (child)->draw) ( + child, drawable, x, y, width, height); + } +} + +/* Point handler for canvas groups */ +static double +gnome_canvas_group_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item) +{ + GnomeCanvasGroup *group; + GList *list; + GnomeCanvasItem *child, *point_item; + int x1, y1, x2, y2; + double gx, gy; + double dist, best; + int has_point; + + group = GNOME_CANVAS_GROUP (item); + + x1 = cx - item->canvas->close_enough; + y1 = cy - item->canvas->close_enough; + x2 = cx + item->canvas->close_enough; + y2 = cy + item->canvas->close_enough; + + best = 0.0; + *actual_item = NULL; + + gx = x; + gy = y; + + dist = 0.0; /* keep gcc happy */ + + for (list = group->item_list; list; list = list->next) { + child = list->data; + + if ((child->x1 > x2) || (child->y1 > y2) || (child->x2 < x1) || (child->y2 < y1)) + continue; + + point_item = NULL; /* cater for incomplete item implementations */ + + if ((child->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + && GNOME_CANVAS_ITEM_GET_CLASS (child)->point) { + dist = gnome_canvas_item_invoke_point (child, gx, gy, cx, cy, &point_item); + has_point = TRUE; + } else + has_point = FALSE; + + if (has_point + && point_item + && ((int) (dist * item->canvas->pixels_per_unit + 0.5) + <= item->canvas->close_enough)) { + best = dist; + *actual_item = point_item; + } + } + + return best; +} + +/* Bounds handler for canvas groups */ +static void +gnome_canvas_group_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + GnomeCanvasGroup *group; + GnomeCanvasItem *child; + GList *list; + double tx1, ty1, tx2, ty2; + double minx, miny, maxx, maxy; + int set; + + group = GNOME_CANVAS_GROUP (item); + + /* Get the bounds of the first visible item */ + + child = NULL; /* Unnecessary but eliminates a warning. */ + + set = FALSE; + + for (list = group->item_list; list; list = list->next) { + child = list->data; + + if (child->object.flags & GNOME_CANVAS_ITEM_VISIBLE) { + set = TRUE; + gnome_canvas_item_get_bounds (child, &minx, &miny, &maxx, &maxy); + break; + } + } + + /* If there were no visible items, return an empty bounding box */ + + if (!set) { + *x1 = *y1 = *x2 = *y2 = 0.0; + return; + } + + /* Now we can grow the bounds using the rest of the items */ + + list = list->next; + + for (; list; list = list->next) { + child = list->data; + + if (!(child->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) + continue; + + gnome_canvas_item_get_bounds (child, &tx1, &ty1, &tx2, &ty2); + + if (tx1 < minx) + minx = tx1; + + if (ty1 < miny) + miny = ty1; + + if (tx2 > maxx) + maxx = tx2; + + if (ty2 > maxy) + maxy = ty2; + } + + *x1 = minx; + *y1 = miny; + *x2 = maxx; + *y2 = maxy; +} + +/* Render handler for canvas groups */ +static void +gnome_canvas_group_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) +{ + GnomeCanvasGroup *group; + GnomeCanvasItem *child; + GList *list; + + group = GNOME_CANVAS_GROUP (item); + + for (list = group->item_list; list; list = list->next) { + child = list->data; + + if (((child->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + && ((child->x1 < buf->rect.x1) + && (child->y1 < buf->rect.y1) + && (child->x2 > buf->rect.x0) + && (child->y2 > buf->rect.y0))) + || ((GTK_OBJECT_FLAGS (child) & GNOME_CANVAS_ITEM_ALWAYS_REDRAW) + && (child->x1 < child->canvas->redraw_x2) + && (child->y1 < child->canvas->redraw_y2) + && (child->x2 > child->canvas->redraw_x1) + && (child->y2 > child->canvas->redraw_y2))) + if (GNOME_CANVAS_ITEM_GET_CLASS (child)->render) + (* GNOME_CANVAS_ITEM_GET_CLASS (child)->render) ( + child, buf); + } +} + +/* Adds an item to a group */ +static void +group_add (GnomeCanvasGroup *group, GnomeCanvasItem *item) +{ + g_object_ref_sink (G_OBJECT (item)); + + if (!group->item_list) { + group->item_list = g_list_append (group->item_list, item); + group->item_list_end = group->item_list; + } else + group->item_list_end = g_list_append (group->item_list_end, item)->next; + + if (group->item.object.flags & GNOME_CANVAS_ITEM_REALIZED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->realize) (item); + + if (group->item.object.flags & GNOME_CANVAS_ITEM_MAPPED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->map) (item); + + g_object_notify (G_OBJECT (item), "parent"); +} + +/* Removes an item from a group */ +static void +group_remove (GnomeCanvasGroup *group, GnomeCanvasItem *item) +{ + GList *children; + + g_return_if_fail (GNOME_IS_CANVAS_GROUP (group)); + g_return_if_fail (GNOME_IS_CANVAS_ITEM (item)); + + for (children = group->item_list; children; children = children->next) + if (children->data == item) { + if (item->object.flags & GNOME_CANVAS_ITEM_MAPPED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->unmap) (item); + + if (item->object.flags & GNOME_CANVAS_ITEM_REALIZED) + (* GNOME_CANVAS_ITEM_GET_CLASS (item)->unrealize) (item); + + /* Unparent the child */ + + item->parent = NULL; + g_object_unref (G_OBJECT (item)); + + /* Remove it from the list */ + + if (children == group->item_list_end) + group->item_list_end = children->prev; + + group->item_list = g_list_remove_link (group->item_list, children); + g_list_free (children); + break; + } +} + + +/*** GnomeCanvas ***/ + + +enum { + DRAW_BACKGROUND, + RENDER_BACKGROUND, + LAST_SIGNAL +}; + +static void gnome_canvas_class_init (GnomeCanvasClass *class); +static void gnome_canvas_init (GnomeCanvas *canvas); +static void gnome_canvas_destroy (GtkObject *object); +static void gnome_canvas_map (GtkWidget *widget); +static void gnome_canvas_unmap (GtkWidget *widget); +static void gnome_canvas_realize (GtkWidget *widget); +static void gnome_canvas_unrealize (GtkWidget *widget); +static void gnome_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gint gnome_canvas_button (GtkWidget *widget, + GdkEventButton *event); +static gint gnome_canvas_motion (GtkWidget *widget, + GdkEventMotion *event); +static gint gnome_canvas_expose (GtkWidget *widget, + GdkEventExpose *event); +static gboolean gnome_canvas_key (GtkWidget *widget, + GdkEventKey *event); +static gint gnome_canvas_crossing (GtkWidget *widget, + GdkEventCrossing *event); +static gint gnome_canvas_focus_in (GtkWidget *widget, + GdkEventFocus *event); +static gint gnome_canvas_focus_out (GtkWidget *widget, + GdkEventFocus *event); +static void gnome_canvas_request_update_real (GnomeCanvas *canvas); +static void gnome_canvas_draw_background (GnomeCanvas *canvas, + GdkDrawable *drawable, + int x, + int y, + int width, + int height); + + +static GtkLayoutClass *canvas_parent_class; + +static guint canvas_signals[LAST_SIGNAL]; + +enum { + PROP_AA = 1, + PROP_FOCUSED_ITEM +}; + +/** + * gnome_canvas_get_type: + * + * Registers the &GnomeCanvas class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the &GnomeCanvas class. + **/ +GType +gnome_canvas_get_type (void) +{ + static GType canvas_type; + + if (!canvas_type) { + const GTypeInfo object_info = { + sizeof (GnomeCanvasClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gnome_canvas_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (GnomeCanvas), + 0, /* n_preallocs */ + (GInstanceInitFunc) gnome_canvas_init, + NULL /* value_table */ + }; + + canvas_type = g_type_register_static (GTK_TYPE_LAYOUT, "GnomeCanvas", + &object_info, 0); + } + + return canvas_type; +} + +static void +gnome_canvas_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_AA: + g_value_set_boolean (value, GNOME_CANVAS (object)->aa); + break; + case PROP_FOCUSED_ITEM: + g_value_set_object (value, GNOME_CANVAS (object)->focused_item); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gnome_canvas_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) { + case PROP_AA: + GNOME_CANVAS (object)->aa = g_value_get_boolean (value); + break; + case PROP_FOCUSED_ITEM: + GNOME_CANVAS (object)->focused_item = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* Class initialization function for GnomeCanvasClass */ +static void +gnome_canvas_class_init (GnomeCanvasClass *klass) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass *)klass; + object_class = (GtkObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + canvas_parent_class = g_type_class_peek_parent (klass); + + gobject_class->set_property = gnome_canvas_set_property; + gobject_class->get_property = gnome_canvas_get_property; + + object_class->destroy = gnome_canvas_destroy; + + widget_class->map = gnome_canvas_map; + widget_class->unmap = gnome_canvas_unmap; + widget_class->realize = gnome_canvas_realize; + widget_class->unrealize = gnome_canvas_unrealize; + widget_class->size_allocate = gnome_canvas_size_allocate; + widget_class->button_press_event = gnome_canvas_button; + widget_class->button_release_event = gnome_canvas_button; + widget_class->motion_notify_event = gnome_canvas_motion; + widget_class->expose_event = gnome_canvas_expose; + widget_class->key_press_event = gnome_canvas_key; + widget_class->key_release_event = gnome_canvas_key; + widget_class->enter_notify_event = gnome_canvas_crossing; + widget_class->leave_notify_event = gnome_canvas_crossing; + widget_class->focus_in_event = gnome_canvas_focus_in; + widget_class->focus_out_event = gnome_canvas_focus_out; + + klass->draw_background = gnome_canvas_draw_background; + klass->render_background = NULL; + klass->request_update = gnome_canvas_request_update_real; + + g_object_class_install_property (G_OBJECT_CLASS (object_class), + PROP_AA, + g_param_spec_boolean ("aa", + _("Antialiased"), + _("The antialiasing mode of the canvas."), + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (gobject_class, PROP_FOCUSED_ITEM, + g_param_spec_object ("focused_item", NULL, NULL, + GNOME_TYPE_CANVAS_ITEM, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + canvas_signals[DRAW_BACKGROUND] = + g_signal_new ("draw_background", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GnomeCanvasClass, draw_background), + NULL, NULL, + gnome_canvas_marshal_VOID__OBJECT_INT_INT_INT_INT, + G_TYPE_NONE, 5, GDK_TYPE_DRAWABLE, + G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); + canvas_signals[RENDER_BACKGROUND] = + g_signal_new ("render_background", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GnomeCanvasClass, render_background), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + gail_canvas_init(); +} + +/* Callback used when the root item of a canvas is destroyed. The user should + * never ever do this, so we panic if this happens. + */ +G_GNUC_NORETURN static void +panic_root_destroyed (GtkObject *object, gpointer data) +{ + g_error ("Eeeek, root item %p of canvas %p was destroyed!", object, data); +} + +/* Object initialization function for GnomeCanvas */ +static void +gnome_canvas_init (GnomeCanvas *canvas) +{ + GTK_WIDGET_SET_FLAGS (canvas, GTK_CAN_FOCUS); + + canvas->need_update = FALSE; + canvas->need_redraw = FALSE; + canvas->redraw_area = NULL; + canvas->idle_id = 0; + + canvas->scroll_x1 = 0.0; + canvas->scroll_y1 = 0.0; + canvas->scroll_x2 = canvas->layout.width; + canvas->scroll_y2 = canvas->layout.height; + + canvas->pixels_per_unit = 1.0; + + canvas->pick_event.type = GDK_LEAVE_NOTIFY; + canvas->pick_event.crossing.x = 0; + canvas->pick_event.crossing.y = 0; + + canvas->dither = GDK_RGB_DITHER_MAX; + + /* This may not be what people want, but it is set to be turned on by + * default to have the same initial behavior as the canvas in GNOME 1.4. + */ + canvas->center_scroll_region = TRUE; + + gtk_layout_set_hadjustment (GTK_LAYOUT (canvas), NULL); + gtk_layout_set_vadjustment (GTK_LAYOUT (canvas), NULL); + + /* Disable the gtk+ double buffering since the canvas uses it's own. */ + gtk_widget_set_double_buffered (GTK_WIDGET (canvas), FALSE); + + /* Create the root item as a special case */ + + canvas->root = GNOME_CANVAS_ITEM (g_object_new (gnome_canvas_group_get_type (), NULL)); + canvas->root->canvas = canvas; + + g_object_ref_sink (canvas->root); + + canvas->root_destroy_id = g_signal_connect (canvas->root, "destroy", + G_CALLBACK (panic_root_destroyed), + canvas); + + canvas->need_repick = TRUE; +} + +/* Convenience function to remove the idle handler of a canvas */ +static void +remove_idle (GnomeCanvas *canvas) +{ + if (canvas->idle_id == 0) + return; + + g_source_remove (canvas->idle_id); + canvas->idle_id = 0; +} + +/* Removes the transient state of the canvas (idle handler, grabs). */ +static void +shutdown_transients (GnomeCanvas *canvas) +{ + /* We turn off the need_redraw flag, since if the canvas is mapped again + * it will request a redraw anyways. We do not turn off the need_update + * flag, though, because updates are not queued when the canvas remaps + * itself. + */ + if (canvas->need_redraw) { + canvas->need_redraw = FALSE; + art_uta_free (canvas->redraw_area); + canvas->redraw_area = NULL; + canvas->redraw_x1 = 0; + canvas->redraw_y1 = 0; + canvas->redraw_x2 = 0; + canvas->redraw_y2 = 0; + } + + if (canvas->grabbed_item) { + canvas->grabbed_item = NULL; + gdk_pointer_ungrab (GDK_CURRENT_TIME); + } + + remove_idle (canvas); +} + +/* Destroy handler for GnomeCanvas */ +static void +gnome_canvas_destroy (GtkObject *object) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (object)); + + /* remember, destroy can be run multiple times! */ + + canvas = GNOME_CANVAS (object); + + if (canvas->root_destroy_id) { + g_signal_handler_disconnect (canvas->root, canvas->root_destroy_id); + canvas->root_destroy_id = 0; + } + if (canvas->root) { + gtk_object_destroy (GTK_OBJECT (canvas->root)); + g_object_unref (G_OBJECT (canvas->root)); + canvas->root = NULL; + } + + shutdown_transients (canvas); + + if (GTK_OBJECT_CLASS (canvas_parent_class)->destroy) + (* GTK_OBJECT_CLASS (canvas_parent_class)->destroy) (object); +} + +/** + * gnome_canvas_new: + * + * Creates a new empty canvas in non-antialiased mode. + * + * Return value: A newly-created canvas. + **/ +GtkWidget * +gnome_canvas_new (void) +{ + return GTK_WIDGET (g_object_new (gnome_canvas_get_type (), NULL)); +} + +/** + * gnome_canvas_new_aa: + * + * Creates a new empty canvas in antialiased mode. + * + * Return value: A newly-created antialiased canvas. + **/ +GtkWidget * +gnome_canvas_new_aa (void) +{ + return GTK_WIDGET (g_object_new (GNOME_TYPE_CANVAS, + "aa", TRUE, + NULL)); +} + +/* Map handler for the canvas */ +static void +gnome_canvas_map (GtkWidget *widget) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (widget)); + + /* Normal widget mapping stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->map) + (* GTK_WIDGET_CLASS (canvas_parent_class)->map) (widget); + + canvas = GNOME_CANVAS (widget); + + if (canvas->need_update) + add_idle (canvas); + + /* Map items */ + + if (GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->map) + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->map) (canvas->root); +} + +/* Unmap handler for the canvas */ +static void +gnome_canvas_unmap (GtkWidget *widget) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (widget)); + + canvas = GNOME_CANVAS (widget); + + shutdown_transients (canvas); + + /* Unmap items */ + + if (GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap) + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->unmap) (canvas->root); + + /* Normal widget unmapping stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unmap) + (* GTK_WIDGET_CLASS (canvas_parent_class)->unmap) (widget); +} + +/* Realize handler for the canvas */ +static void +gnome_canvas_realize (GtkWidget *widget) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (widget)); + + /* Normal widget realization stuff */ + + if (GTK_WIDGET_CLASS (canvas_parent_class)->realize) + (* GTK_WIDGET_CLASS (canvas_parent_class)->realize) (widget); + + canvas = GNOME_CANVAS (widget); + + gdk_window_set_events (canvas->layout.bin_window, + (gdk_window_get_events (canvas->layout.bin_window) + | GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK + | GDK_FOCUS_CHANGE_MASK)); + + /* Create our own temporary pixmap gc and realize all the items */ + + canvas->pixmap_gc = gdk_gc_new (canvas->layout.bin_window); + + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->realize) (canvas->root); +} + +/* Unrealize handler for the canvas */ +static void +gnome_canvas_unrealize (GtkWidget *widget) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (widget)); + + canvas = GNOME_CANVAS (widget); + + shutdown_transients (canvas); + + /* Unrealize items and parent widget */ + + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->unrealize) (canvas->root); + + g_object_unref (canvas->pixmap_gc); + canvas->pixmap_gc = NULL; + + if (GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) + (* GTK_WIDGET_CLASS (canvas_parent_class)->unrealize) (widget); +} + +/* Handles scrolling of the canvas. Adjusts the scrolling and zooming offset to + * keep as much as possible of the canvas scrolling region in view. + */ +static void +scroll_to (GnomeCanvas *canvas, int cx, int cy) +{ + int scroll_width, scroll_height; + int right_limit, bottom_limit; + int old_zoom_xofs, old_zoom_yofs; + int changed_x = FALSE, changed_y = FALSE; + int canvas_width, canvas_height; + + canvas_width = GTK_WIDGET (canvas)->allocation.width; + canvas_height = GTK_WIDGET (canvas)->allocation.height; + + scroll_width = floor ((canvas->scroll_x2 - canvas->scroll_x1) * canvas->pixels_per_unit + + 0.5); + scroll_height = floor ((canvas->scroll_y2 - canvas->scroll_y1) * canvas->pixels_per_unit + + 0.5); + + right_limit = scroll_width - canvas_width; + bottom_limit = scroll_height - canvas_height; + + old_zoom_xofs = canvas->zoom_xofs; + old_zoom_yofs = canvas->zoom_yofs; + + if (right_limit < 0) { + cx = 0; + + if (canvas->center_scroll_region) { + canvas->zoom_xofs = (canvas_width - scroll_width) / 2; + scroll_width = canvas_width; + } else + canvas->zoom_xofs = 0; + } else if (cx < 0) { + cx = 0; + canvas->zoom_xofs = 0; + } else if (cx > right_limit) { + cx = right_limit; + canvas->zoom_xofs = 0; + } else + canvas->zoom_xofs = 0; + + if (bottom_limit < 0) { + cy = 0; + + if (canvas->center_scroll_region) { + canvas->zoom_yofs = (canvas_height - scroll_height) / 2; + scroll_height = canvas_height; + } else + canvas->zoom_yofs = 0; + } else if (cy < 0) { + cy = 0; + canvas->zoom_yofs = 0; + } else if (cy > bottom_limit) { + cy = bottom_limit; + canvas->zoom_yofs = 0; + } else + canvas->zoom_yofs = 0; + + if ((canvas->zoom_xofs != old_zoom_xofs) || (canvas->zoom_yofs != old_zoom_yofs)) { + /* This can only occur, if either canvas size or widget size changes */ + /* So I think we can request full redraw here */ + /* The reason is, that coverage UTA will be invalidated by offset change */ + /* fixme: Strictly this is not correct - we have to remove our own idle (Lauris) */ + /* More stuff - we have to mark root as needing fresh affine (Lauris) */ + if (!(canvas->root->object.flags & GNOME_CANVAS_ITEM_NEED_AFFINE)) { + canvas->root->object.flags |= GNOME_CANVAS_ITEM_NEED_AFFINE; + gnome_canvas_request_update (canvas); + } + gtk_widget_queue_draw (GTK_WIDGET (canvas)); + } + + if (canvas->layout.hadjustment && ((int) canvas->layout.hadjustment->value) != cx) { + canvas->layout.hadjustment->value = cx; + changed_x = TRUE; + } + + if (canvas->layout.vadjustment && ((int) canvas->layout.vadjustment->value) != cy) { + canvas->layout.vadjustment->value = cy; + changed_y = TRUE; + } + + if ((scroll_width != (int) canvas->layout.width) + || (scroll_height != (int) canvas->layout.height)) + gtk_layout_set_size (GTK_LAYOUT (canvas), scroll_width, scroll_height); + + /* Signal GtkLayout that it should do a redraw. */ + + if (changed_x) + g_signal_emit_by_name (canvas->layout.hadjustment, "value_changed"); + + if (changed_y) + g_signal_emit_by_name (canvas->layout.vadjustment, "value_changed"); +} + +/* Size allocation handler for the canvas */ +static void +gnome_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + GnomeCanvas *canvas; + + g_return_if_fail (GNOME_IS_CANVAS (widget)); + g_return_if_fail (allocation != NULL); + + if (GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate) + (* GTK_WIDGET_CLASS (canvas_parent_class)->size_allocate) (widget, allocation); + + canvas = GNOME_CANVAS (widget); + + /* Recenter the view, if appropriate */ + + canvas->layout.hadjustment->page_size = allocation->width; + canvas->layout.hadjustment->page_increment = allocation->width / 2; + + canvas->layout.vadjustment->page_size = allocation->height; + canvas->layout.vadjustment->page_increment = allocation->height / 2; + + scroll_to (canvas, + canvas->layout.hadjustment->value, + canvas->layout.vadjustment->value); + + g_signal_emit_by_name (canvas->layout.hadjustment, "changed"); + g_signal_emit_by_name (canvas->layout.vadjustment, "changed"); +} + +/* Emits an event for an item in the canvas, be it the current item, grabbed + * item, or focused item, as appropriate. + */ + +static int +emit_event (GnomeCanvas *canvas, GdkEvent *event) +{ + GdkEvent *ev; + gint finished; + GnomeCanvasItem *item; + GnomeCanvasItem *parent; + guint mask; + + /* Perform checks for grabbed items */ + + if (canvas->grabbed_item && + !is_descendant (canvas->current_item, canvas->grabbed_item)) { + /* I think this warning is annoying and I don't know what it's for + * so I'll disable it for now. + */ +/* g_warning ("emit_event() returning FALSE!\n");*/ + return FALSE; + } + + if (canvas->grabbed_item) { + switch (event->type) { + case GDK_ENTER_NOTIFY: + mask = GDK_ENTER_NOTIFY_MASK; + break; + + case GDK_LEAVE_NOTIFY: + mask = GDK_LEAVE_NOTIFY_MASK; + break; + + case GDK_MOTION_NOTIFY: + mask = GDK_POINTER_MOTION_MASK; + break; + + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + mask = GDK_BUTTON_PRESS_MASK; + break; + + case GDK_BUTTON_RELEASE: + mask = GDK_BUTTON_RELEASE_MASK; + break; + + case GDK_KEY_PRESS: + mask = GDK_KEY_PRESS_MASK; + break; + + case GDK_KEY_RELEASE: + mask = GDK_KEY_RELEASE_MASK; + break; + + case GDK_SCROLL: + mask = GDK_SCROLL_MASK; + break; + + default: + mask = 0; + break; + } + + if (!(mask & canvas->grabbed_event_mask)) + return FALSE; + } + + /* Convert to world coordinates -- we have two cases because of diferent + * offsets of the fields in the event structures. + */ + + ev = gdk_event_copy (event); + + switch (ev->type) + { + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + gnome_canvas_window_to_world (canvas, + ev->crossing.x, ev->crossing.y, + &ev->crossing.x, &ev->crossing.y); + break; + + case GDK_MOTION_NOTIFY: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + gnome_canvas_window_to_world (canvas, + ev->motion.x, ev->motion.y, + &ev->motion.x, &ev->motion.y); + break; + + default: + break; + } + + /* Choose where we send the event */ + + item = canvas->current_item; + + if (canvas->focused_item + && ((event->type == GDK_KEY_PRESS) || + (event->type == GDK_KEY_RELEASE) || + (event->type == GDK_FOCUS_CHANGE))) + item = canvas->focused_item; + + /* The event is propagated up the hierarchy (for if someone connected to + * a group instead of a leaf event), and emission is stopped if a + * handler returns TRUE, just like for GtkWidget events. + */ + + finished = FALSE; + + while (item && !finished) { + g_object_ref (G_OBJECT (item)); + + g_signal_emit (item, item_signals[ITEM_EVENT], 0, + ev, &finished); + + parent = item->parent; + g_object_unref (G_OBJECT (item)); + + item = parent; + } + + gdk_event_free (ev); + + return finished; +} + +/* Re-picks the current item in the canvas, based on the event's coordinates. + * Also emits enter/leave events for items as appropriate. + */ +static int +pick_current_item (GnomeCanvas *canvas, GdkEvent *event) +{ + int button_down; + double x, y; + int cx, cy; + int retval; + + retval = FALSE; + + /* If a button is down, we'll perform enter and leave events on the + * current item, but not enter on any other item. This is more or less + * like X pointer grabbing for canvas items. + */ + button_down = canvas->state & (GDK_BUTTON1_MASK + | GDK_BUTTON2_MASK + | GDK_BUTTON3_MASK + | GDK_BUTTON4_MASK + | GDK_BUTTON5_MASK); + if (!button_down) + canvas->left_grabbed_item = FALSE; + + /* Save the event in the canvas. This is used to synthesize enter and + * leave events in case the current item changes. It is also used to + * re-pick the current item if the current one gets deleted. Also, + * synthesize an enter event. + */ + if (event != &canvas->pick_event) { + if ((event->type == GDK_MOTION_NOTIFY) || (event->type == GDK_BUTTON_RELEASE)) { + /* these fields have the same offsets in both types of events */ + + canvas->pick_event.crossing.type = GDK_ENTER_NOTIFY; + canvas->pick_event.crossing.window = event->motion.window; + canvas->pick_event.crossing.send_event = event->motion.send_event; + canvas->pick_event.crossing.subwindow = NULL; + canvas->pick_event.crossing.x = event->motion.x; + canvas->pick_event.crossing.y = event->motion.y; + canvas->pick_event.crossing.mode = GDK_CROSSING_NORMAL; + canvas->pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; + canvas->pick_event.crossing.focus = FALSE; + canvas->pick_event.crossing.state = event->motion.state; + + /* these fields don't have the same offsets in both types of events */ + + if (event->type == GDK_MOTION_NOTIFY) { + canvas->pick_event.crossing.x_root = event->motion.x_root; + canvas->pick_event.crossing.y_root = event->motion.y_root; + } else { + canvas->pick_event.crossing.x_root = event->button.x_root; + canvas->pick_event.crossing.y_root = event->button.y_root; + } + } else + canvas->pick_event = *event; + } + + /* Don't do anything else if this is a recursive call */ + + if (canvas->in_repick) + return retval; + + /* LeaveNotify means that there is no current item, so we don't look for one */ + + if (canvas->pick_event.type != GDK_LEAVE_NOTIFY) { + /* these fields don't have the same offsets in both types of events */ + + if (canvas->pick_event.type == GDK_ENTER_NOTIFY) { + x = canvas->pick_event.crossing.x - canvas->zoom_xofs; + y = canvas->pick_event.crossing.y - canvas->zoom_yofs; + } else { + x = canvas->pick_event.motion.x - canvas->zoom_xofs; + y = canvas->pick_event.motion.y - canvas->zoom_yofs; + } + + /* canvas pixel coords */ + + cx = (int) (x + 0.5); + cy = (int) (y + 0.5); + + /* world coords */ + + x = canvas->scroll_x1 + x / canvas->pixels_per_unit; + y = canvas->scroll_y1 + y / canvas->pixels_per_unit; + + /* find the closest item */ + + if (canvas->root->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + gnome_canvas_item_invoke_point (canvas->root, x, y, cx, cy, + &canvas->new_current_item); + else + canvas->new_current_item = NULL; + } else + canvas->new_current_item = NULL; + + if ((canvas->new_current_item == canvas->current_item) && !canvas->left_grabbed_item) + return retval; /* current item did not change */ + + /* Synthesize events for old and new current items */ + + if ((canvas->new_current_item != canvas->current_item) + && (canvas->current_item != NULL) + && !canvas->left_grabbed_item) { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_LEAVE_NOTIFY; + + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + canvas->in_repick = TRUE; + retval = emit_event (canvas, &new_event); + canvas->in_repick = FALSE; + } + + /* new_current_item may have been set to NULL during the call to emit_event() above */ + + if ((canvas->new_current_item != canvas->current_item) && button_down) { + canvas->left_grabbed_item = TRUE; + return retval; + } + + /* Handle the rest of cases */ + + canvas->left_grabbed_item = FALSE; + canvas->current_item = canvas->new_current_item; + + if (canvas->current_item != NULL) { + GdkEvent new_event; + + new_event = canvas->pick_event; + new_event.type = GDK_ENTER_NOTIFY; + new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; + new_event.crossing.subwindow = NULL; + retval = emit_event (canvas, &new_event); + } + + return retval; +} + +/* Button event handler for the canvas */ +static gint +gnome_canvas_button (GtkWidget *widget, GdkEventButton *event) +{ + GnomeCanvas *canvas; + int mask; + int retval; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + retval = FALSE; + + canvas = GNOME_CANVAS (widget); + + /* + * dispatch normally regardless of the event's window if an item has + * has a pointer grab in effect + */ + if (!canvas->grabbed_item && event->window != canvas->layout.bin_window) + return retval; + + switch (event->button) { + case 1: + mask = GDK_BUTTON1_MASK; + break; + case 2: + mask = GDK_BUTTON2_MASK; + break; + case 3: + mask = GDK_BUTTON3_MASK; + break; + case 4: + mask = GDK_BUTTON4_MASK; + break; + case 5: + mask = GDK_BUTTON5_MASK; + break; + default: + mask = 0; + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + /* Pick the current item as if the button were not pressed, and + * then process the event. + */ + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + canvas->state ^= mask; + retval = emit_event (canvas, (GdkEvent *) event); + break; + + case GDK_BUTTON_RELEASE: + /* Process the event as if the button were pressed, then repick + * after the button has been released + */ + canvas->state = event->state; + retval = emit_event (canvas, (GdkEvent *) event); + event->state ^= mask; + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + event->state ^= mask; + break; + + default: + g_assert_not_reached (); + } + + return retval; +} + +/* Motion event handler for the canvas */ +static gint +gnome_canvas_motion (GtkWidget *widget, GdkEventMotion *event) +{ + GnomeCanvas *canvas; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = GNOME_CANVAS (widget); + + if (event->window != canvas->layout.bin_window) + return FALSE; + + canvas->state = event->state; + pick_current_item (canvas, (GdkEvent *) event); + return emit_event (canvas, (GdkEvent *) event); +} + +/* Key event handler for the canvas */ +static gboolean +gnome_canvas_key (GtkWidget *widget, GdkEventKey *event) +{ + GnomeCanvas *canvas; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = GNOME_CANVAS (widget); + + if (!emit_event (canvas, (GdkEvent *) event)) { + GtkWidgetClass *widget_class; + + widget_class = GTK_WIDGET_CLASS (canvas_parent_class); + + if (event->type == GDK_KEY_PRESS) { + if (widget_class->key_press_event) + return (* widget_class->key_press_event) (widget, event); + } else if (event->type == GDK_KEY_RELEASE) { + if (widget_class->key_release_event) + return (* widget_class->key_release_event) (widget, event); + } else + g_assert_not_reached (); + + return FALSE; + } else + return TRUE; +} + + +/* Crossing event handler for the canvas */ +static gint +gnome_canvas_crossing (GtkWidget *widget, GdkEventCrossing *event) +{ + GnomeCanvas *canvas; + + g_return_val_if_fail (GNOME_IS_CANVAS (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + canvas = GNOME_CANVAS (widget); + + if (event->window != canvas->layout.bin_window) + return FALSE; + + canvas->state = event->state; + return pick_current_item (canvas, (GdkEvent *) event); +} + +/* Focus in handler for the canvas */ +static gint +gnome_canvas_focus_in (GtkWidget *widget, GdkEventFocus *event) +{ + GnomeCanvas *canvas; + + GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); + + canvas = GNOME_CANVAS (widget); + + if (canvas->focused_item) + return emit_event (canvas, (GdkEvent *) event); + else + return FALSE; +} + +/* Focus out handler for the canvas */ +static gint +gnome_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + GnomeCanvas *canvas; + + GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); + + canvas = GNOME_CANVAS (widget); + + if (canvas->focused_item) + return emit_event (canvas, (GdkEvent *) event); + else + return FALSE; +} + +#define REDRAW_QUANTUM_SIZE 512 + +static void +gnome_canvas_paint_rect (GnomeCanvas *canvas, gint x0, gint y0, gint x1, gint y1) +{ + GtkWidget *widget; + gint draw_x1, draw_y1; + gint draw_x2, draw_y2; + gint draw_width, draw_height; + + g_return_if_fail (!canvas->need_update); + + widget = GTK_WIDGET (canvas); + + draw_x1 = MAX (x0, canvas->layout.hadjustment->value - canvas->zoom_xofs); + draw_y1 = MAX (y0, canvas->layout.vadjustment->value - canvas->zoom_yofs); + draw_x2 = MIN (draw_x1 + GTK_WIDGET (canvas)->allocation.width, x1); + draw_y2 = MIN (draw_y1 + GTK_WIDGET (canvas)->allocation.height, y1); + + draw_width = draw_x2 - draw_x1; + draw_height = draw_y2 - draw_y1; + + if (draw_width < 1 || draw_height < 1) + return; + + canvas->redraw_x1 = draw_x1; + canvas->redraw_y1 = draw_y1; + canvas->redraw_x2 = draw_x2; + canvas->redraw_y2 = draw_y2; + canvas->draw_xofs = draw_x1; + canvas->draw_yofs = draw_y1; + + if (canvas->aa) { + GnomeCanvasBuf buf; + guchar *px; + GdkColor *color; + + px = g_new (guchar, draw_width * 3 * draw_height); + + buf.buf = px; + buf.buf_rowstride = draw_width * 3; + buf.rect.x0 = draw_x1; + buf.rect.y0 = draw_y1; + buf.rect.x1 = draw_x2; + buf.rect.y1 = draw_y2; + color = &widget->style->bg[GTK_STATE_NORMAL]; + buf.bg_color = (((color->red & 0xff00) << 8) | (color->green & 0xff00) | (color->blue >> 8)); + buf.is_bg = 1; + buf.is_buf = 0; + + g_signal_emit (G_OBJECT (canvas), canvas_signals[RENDER_BACKGROUND], 0, &buf); + + if (canvas->root->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->render) (canvas->root, &buf); + + if (buf.is_bg) { + gdk_gc_set_rgb_fg_color (canvas->pixmap_gc, color); + gdk_draw_rectangle (canvas->layout.bin_window, + canvas->pixmap_gc, + TRUE, + (draw_x1 + canvas->zoom_xofs), + (draw_y1 + canvas->zoom_yofs), + draw_width, draw_height); + } else { + gdk_draw_rgb_image_dithalign (canvas->layout.bin_window, + canvas->pixmap_gc, + (draw_x1 + canvas->zoom_xofs), + (draw_y1 + canvas->zoom_yofs), + draw_width, draw_height, + canvas->dither, + buf.buf, + buf.buf_rowstride, + draw_x1, draw_y1); + } + + g_free (px); + } else { + GdkPixmap *pixmap; + + pixmap = gdk_pixmap_new (canvas->layout.bin_window, + draw_width, draw_height, + gtk_widget_get_visual (widget)->depth); + + g_signal_emit (G_OBJECT (canvas), canvas_signals[DRAW_BACKGROUND], 0, pixmap, + draw_x1, draw_y1, draw_width, draw_height); + + if (canvas->root->object.flags & GNOME_CANVAS_ITEM_VISIBLE) + (* GNOME_CANVAS_ITEM_GET_CLASS (canvas->root)->draw) ( + canvas->root, pixmap, + draw_x1, draw_y1, + draw_width, draw_height); + + /* Copy the pixmap to the window and clean up */ + + gdk_draw_drawable (canvas->layout.bin_window, + canvas->pixmap_gc, + pixmap, + 0, 0, + draw_x1 + canvas->zoom_xofs, + draw_y1 + canvas->zoom_yofs, + draw_width, draw_height); + + g_object_unref (pixmap); + } +} + +/* Expose handler for the canvas */ +static gint +gnome_canvas_expose (GtkWidget *widget, GdkEventExpose *event) +{ + GnomeCanvas *canvas; + GdkRectangle *rects; + gint n_rects; + int i; + + canvas = GNOME_CANVAS (widget); + + if (!gtk_widget_is_drawable (widget) || (event->window != canvas->layout.bin_window)) + return FALSE; + +#ifdef VERBOSE + g_print ("Expose\n"); +#endif + + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (i = 0; i < n_rects; i++) { + ArtIRect rect; + + rect.x0 = rects[i].x - canvas->zoom_xofs; + rect.y0 = rects[i].y - canvas->zoom_yofs; + rect.x1 = rects[i].x + rects[i].width - canvas->zoom_xofs; + rect.y1 = rects[i].y + rects[i].height - canvas->zoom_yofs; + + if (canvas->need_update || canvas->need_redraw) { + ArtUta *uta; + + /* Update or drawing is scheduled, so just mark exposed area as dirty */ + uta = art_uta_from_irect (&rect); + gnome_canvas_request_redraw_uta (canvas, uta); + } else { + /* No pending updates, draw exposed area immediately */ + gnome_canvas_paint_rect (canvas, rect.x0, rect.y0, rect.x1, rect.y1); + + /* And call expose on parent container class */ + if (GTK_WIDGET_CLASS (canvas_parent_class)->expose_event) + (* GTK_WIDGET_CLASS (canvas_parent_class)->expose_event) ( + widget, event); + } + } + + g_free (rects); + + return FALSE; +} + +/* Repaints the areas in the canvas that need it */ +static void +paint (GnomeCanvas *canvas) +{ + ArtIRect *rects; + gint n_rects, i; + ArtIRect visible_rect; + GdkRegion *region; + + /* Extract big rectangles from the microtile array */ + + rects = art_rect_list_from_uta (canvas->redraw_area, + REDRAW_QUANTUM_SIZE, REDRAW_QUANTUM_SIZE, + &n_rects); + + art_uta_free (canvas->redraw_area); + canvas->redraw_area = NULL; + canvas->need_redraw = FALSE; + + /* Turn those rectangles into a GdkRegion for exposing */ + + visible_rect.x0 = canvas->layout.hadjustment->value - canvas->zoom_xofs; + visible_rect.y0 = canvas->layout.vadjustment->value - canvas->zoom_yofs; + visible_rect.x1 = visible_rect.x0 + GTK_WIDGET (canvas)->allocation.width; + visible_rect.y1 = visible_rect.y0 + GTK_WIDGET (canvas)->allocation.height; + + region = gdk_region_new (); + + for (i = 0; i < n_rects; i++) { + ArtIRect clipped; + + art_irect_intersect (&clipped, &visible_rect, rects + i); + if (!art_irect_empty (&clipped)) { + GdkRectangle gdkrect; + + gdkrect.x = clipped.x0 + canvas->zoom_xofs; + gdkrect.y = clipped.y0 + canvas->zoom_yofs; + gdkrect.width = clipped.x1 - clipped.x0; + gdkrect.height = clipped.y1 - clipped.y0; + + region = gdk_region_rectangle (&gdkrect); + gdk_window_invalidate_region (canvas->layout.bin_window, region, FALSE); + gdk_region_destroy (region); + } + } + + art_free (rects); + + canvas->redraw_x1 = 0; + canvas->redraw_y1 = 0; + canvas->redraw_x2 = 0; + canvas->redraw_y2 = 0; +} + +static void +gnome_canvas_draw_background (GnomeCanvas *canvas, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + /* By default, we use the style background. */ + gdk_gc_set_foreground (canvas->pixmap_gc, + >K_WIDGET (canvas)->style->bg[GTK_STATE_NORMAL]); + gdk_draw_rectangle (drawable, + canvas->pixmap_gc, + TRUE, + 0, 0, + width, height); +} + +static void +do_update (GnomeCanvas *canvas) +{ + /* Cause the update if necessary */ + +update_again: + if (canvas->need_update) { + gdouble w2cpx[6]; + + /* We start updating root with w2cpx affine */ + w2cpx[0] = canvas->pixels_per_unit; + w2cpx[1] = 0.0; + w2cpx[2] = 0.0; + w2cpx[3] = canvas->pixels_per_unit; + w2cpx[4] = -canvas->scroll_x1 * canvas->pixels_per_unit; + w2cpx[5] = -canvas->scroll_y1 * canvas->pixels_per_unit; + + gnome_canvas_item_invoke_update (canvas->root, w2cpx, NULL, 0); + + canvas->need_update = FALSE; + } + + /* Pick new current item */ + + while (canvas->need_repick) { + canvas->need_repick = FALSE; + pick_current_item (canvas, &canvas->pick_event); + } + + /* it is possible that during picking we emitted an event in which + the user then called some function which then requested update + of something. Without this we'd be left in a state where + need_update would have been left TRUE and the canvas would have + been left unpainted. */ + if (canvas->need_update) { + goto update_again; + } + + /* Paint if able to */ + + if (gtk_widget_is_drawable (GTK_WIDGET (canvas)) && canvas->need_redraw) + paint (canvas); +} + +/* Idle handler for the canvas. It deals with pending updates and redraws. */ +static gboolean +idle_handler (gpointer data) +{ + GnomeCanvas *canvas; + + GDK_THREADS_ENTER (); + + canvas = GNOME_CANVAS (data); + + do_update (canvas); + + /* Reset idle id */ + canvas->idle_id = 0; + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +/* Convenience function to add an idle handler to a canvas */ +static void +add_idle (GnomeCanvas *canvas) +{ + g_assert (canvas->need_update || canvas->need_redraw); + + if (!canvas->idle_id) + canvas->idle_id = g_idle_add_full (CANVAS_IDLE_PRIORITY, + idle_handler, + canvas, + NULL); + +/* canvas->idle_id = gtk_idle_add (idle_handler, canvas); */ +} + +/** + * gnome_canvas_root: + * @canvas: A canvas. + * + * Queries the root group of a canvas. + * + * Return value: The root group of the specified canvas. + **/ +GnomeCanvasGroup * +gnome_canvas_root (GnomeCanvas *canvas) +{ + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), NULL); + + return GNOME_CANVAS_GROUP (canvas->root); +} + + +/** + * gnome_canvas_set_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region. + * @y1: Upper limit of the scrolling region. + * @x2: Rightmost limit of the scrolling region. + * @y2: Lower limit of the scrolling region. + * + * Sets the scrolling region of a canvas to the specified rectangle. The canvas + * will then be able to scroll only within this region. The view of the canvas + * is adjusted as appropriate to display as much of the new region as possible. + **/ +void +gnome_canvas_set_scroll_region (GnomeCanvas *canvas, double x1, double y1, double x2, double y2) +{ + double wxofs, wyofs; + int xofs, yofs; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + /* + * Set the new scrolling region. If possible, do not move the visible contents of the + * canvas. + */ + + gnome_canvas_c2w (canvas, + GTK_LAYOUT (canvas)->hadjustment->value + canvas->zoom_xofs, + GTK_LAYOUT (canvas)->vadjustment->value + canvas->zoom_yofs, + /*canvas->zoom_xofs, + canvas->zoom_yofs,*/ + &wxofs, &wyofs); + + canvas->scroll_x1 = x1; + canvas->scroll_y1 = y1; + canvas->scroll_x2 = x2; + canvas->scroll_y2 = y2; + + gnome_canvas_w2c (canvas, wxofs, wyofs, &xofs, &yofs); + + scroll_to (canvas, xofs, yofs); + + canvas->need_repick = TRUE; +#if 0 + /* todo: should be requesting update */ + (* GNOME_CANVAS_ITEM_CLASS (canvas->root->object.klass)->update) ( + canvas->root, NULL, NULL, 0); +#endif +} + + +/** + * gnome_canvas_get_scroll_region: + * @canvas: A canvas. + * @x1: Leftmost limit of the scrolling region (return value). + * @y1: Upper limit of the scrolling region (return value). + * @x2: Rightmost limit of the scrolling region (return value). + * @y2: Lower limit of the scrolling region (return value). + * + * Queries the scrolling region of a canvas. + **/ +void +gnome_canvas_get_scroll_region (GnomeCanvas *canvas, double *x1, double *y1, double *x2, double *y2) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (x1) + *x1 = canvas->scroll_x1; + + if (y1) + *y1 = canvas->scroll_y1; + + if (x2) + *x2 = canvas->scroll_x2; + + if (y2) + *y2 = canvas->scroll_y2; +} + +/** + * gnome_canvas_set_center_scroll_region: + * @canvas: A canvas. + * @center_scroll_region: Whether to center the scrolling region in the canvas + * window when it is smaller than the canvas' allocation. + * + * When the scrolling region of the canvas is smaller than the canvas window, + * e.g. the allocation of the canvas, it can be either centered on the window + * or simply made to be on the upper-left corner on the window. This function + * lets you configure this property. + **/ +void +gnome_canvas_set_center_scroll_region (GnomeCanvas *canvas, gboolean center_scroll_region) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + canvas->center_scroll_region = center_scroll_region != 0; + + scroll_to (canvas, + canvas->layout.hadjustment->value, + canvas->layout.vadjustment->value); +} + +/** + * gnome_canvas_get_center_scroll_region: + * @canvas: A canvas. + * + * Returns whether the canvas is set to center the scrolling region in the window + * if the former is smaller than the canvas' allocation. + * + * Return value: Whether the scroll region is being centered in the canvas window. + **/ +gboolean +gnome_canvas_get_center_scroll_region (GnomeCanvas *canvas) +{ + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), FALSE); + + return canvas->center_scroll_region ? TRUE : FALSE; +} + +/** + * gnome_canvas_set_pixels_per_unit: + * @canvas: A canvas. + * @n: The number of pixels that correspond to one canvas unit. + * + * Sets the zooming factor of a canvas by specifying the number of pixels that + * correspond to one canvas unit. + * + * The anchor point for zooming, i.e. the point that stays fixed and all others + * zoom inwards or outwards from it, depends on whether the canvas is set to + * center the scrolling region or not. You can control this using the + * gnome_canvas_set_center_scroll_region() function. If the canvas is set to + * center the scroll region, then the center of the canvas window is used as the + * anchor point for zooming. Otherwise, the upper-left corner of the canvas + * window is used as the anchor point. + **/ +void +gnome_canvas_set_pixels_per_unit (GnomeCanvas *canvas, double n) +{ + double ax, ay; + int x1, y1; + int anchor_x, anchor_y; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + g_return_if_fail (n > GNOME_CANVAS_EPSILON); + + if (canvas->center_scroll_region) { + anchor_x = GTK_WIDGET (canvas)->allocation.width / 2; + anchor_y = GTK_WIDGET (canvas)->allocation.height / 2; + } else + anchor_x = anchor_y = 0; + + /* Find the coordinates of the anchor point in units. */ + if(canvas->layout.hadjustment) { + ax = (canvas->layout.hadjustment->value + anchor_x) / canvas->pixels_per_unit + canvas->scroll_x1 + canvas->zoom_xofs; + } else { + ax = (0.0 + anchor_x) / canvas->pixels_per_unit + canvas->scroll_x1 + canvas->zoom_xofs; + } + if(canvas->layout.hadjustment) { + ay = (canvas->layout.vadjustment->value + anchor_y) / canvas->pixels_per_unit + canvas->scroll_y1 + canvas->zoom_yofs; + } else { + ay = (0.0 + anchor_y) / canvas->pixels_per_unit + canvas->scroll_y1 + canvas->zoom_yofs; + } + + /* Now calculate the new offset of the upper left corner. */ + x1 = ((ax - canvas->scroll_x1) * n) - anchor_x; + y1 = ((ay - canvas->scroll_y1) * n) - anchor_y; + + canvas->pixels_per_unit = n; + + scroll_to (canvas, x1, y1); + + if (!(canvas->root->object.flags & GNOME_CANVAS_ITEM_NEED_AFFINE)) { + canvas->root->object.flags |= GNOME_CANVAS_ITEM_NEED_AFFINE; + gnome_canvas_request_update (canvas); + } + + canvas->need_repick = TRUE; +} + +/** + * gnome_canvas_scroll_to: + * @canvas: A canvas. + * @cx: Horizontal scrolling offset in canvas pixel units. + * @cy: Vertical scrolling offset in canvas pixel units. + * + * Makes a canvas scroll to the specified offsets, given in canvas pixel units. + * The canvas will adjust the view so that it is not outside the scrolling + * region. This function is typically not used, as it is better to hook + * scrollbars to the canvas layout's scrolling adjusments. + **/ +void +gnome_canvas_scroll_to (GnomeCanvas *canvas, int cx, int cy) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + scroll_to (canvas, cx, cy); +} + +/** + * gnome_canvas_get_scroll_offsets: + * @canvas: A canvas. + * @cx: Horizontal scrolling offset (return value). + * @cy: Vertical scrolling offset (return value). + * + * Queries the scrolling offsets of a canvas. The values are returned in canvas + * pixel units. + **/ +void +gnome_canvas_get_scroll_offsets (GnomeCanvas *canvas, int *cx, int *cy) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (cx) + *cx = canvas->layout.hadjustment->value; + + if (cy) + *cy = canvas->layout.vadjustment->value; +} + +/** + * gnome_canvas_update_now: + * @canvas: A canvas. + * + * Forces an immediate update and redraw of a canvas. If the canvas does not + * have any pending update or redraw requests, then no action is taken. This is + * typically only used by applications that need explicit control of when the + * display is updated, like games. It is not needed by normal applications. + */ +void +gnome_canvas_update_now (GnomeCanvas *canvas) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (!(canvas->need_update || canvas->need_redraw)) { + g_assert (canvas->idle_id == 0); + g_assert (canvas->redraw_area == NULL); + return; + } + + remove_idle (canvas); + do_update (canvas); +} + +/** + * gnome_canvas_get_item_at: + * @canvas: A canvas. + * @x: X position in world coordinates. + * @y: Y position in world coordinates. + * + * Looks for the item that is under the specified position, which must be + * specified in world coordinates. + * + * Return value: The sought item, or NULL if no item is at the specified + * coordinates. + **/ +GnomeCanvasItem * +gnome_canvas_get_item_at (GnomeCanvas *canvas, double x, double y) +{ + GnomeCanvasItem *item; + double dist; + int cx, cy; + + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), NULL); + + gnome_canvas_w2c (canvas, x, y, &cx, &cy); + + dist = gnome_canvas_item_invoke_point (canvas->root, x, y, cx, cy, &item); + if ((int) (dist * canvas->pixels_per_unit + 0.5) <= canvas->close_enough) + return item; + else + return NULL; +} + +/* Queues an update of the canvas */ +static void +gnome_canvas_request_update (GnomeCanvas *canvas) +{ + GNOME_CANVAS_GET_CLASS (canvas)->request_update (canvas); +} + +static void +gnome_canvas_request_update_real (GnomeCanvas *canvas) +{ + if (canvas->need_update) + return; + + canvas->need_update = TRUE; + if (gtk_widget_get_mapped ((GtkWidget *) canvas)) + add_idle (canvas); +} + +/* Computes the union of two microtile arrays while clipping the result to the + * specified rectangle. Any of the specified utas can be NULL, in which case it + * is taken to be an empty region. + */ +static ArtUta * +uta_union_clip (ArtUta *uta1, ArtUta *uta2, ArtIRect *clip) +{ + ArtUta *uta; + ArtUtaBbox *utiles; + int clip_x1, clip_y1, clip_x2, clip_y2; + int union_x1, union_y1, union_x2, union_y2; + int new_x1, new_y1, new_x2, new_y2; + int x, y; + int ofs, ofs1, ofs2; + + g_assert (clip != NULL); + + /* Compute the tile indices for the clipping rectangle */ + + clip_x1 = clip->x0 >> ART_UTILE_SHIFT; + clip_y1 = clip->y0 >> ART_UTILE_SHIFT; + clip_x2 = (clip->x1 >> ART_UTILE_SHIFT) + 1; + clip_y2 = (clip->y1 >> ART_UTILE_SHIFT) + 1; + + /* Get the union of the bounds of both utas */ + + if (!uta1) { + if (!uta2) + return art_uta_new (clip_x1, clip_y1, clip_x1 + 1, clip_y1 + 1); + + union_x1 = uta2->x0; + union_y1 = uta2->y0; + union_x2 = uta2->x0 + uta2->width; + union_y2 = uta2->y0 + uta2->height; + } else { + if (!uta2) { + union_x1 = uta1->x0; + union_y1 = uta1->y0; + union_x2 = uta1->x0 + uta1->width; + union_y2 = uta1->y0 + uta1->height; + } else { + union_x1 = MIN (uta1->x0, uta2->x0); + union_y1 = MIN (uta1->y0, uta2->y0); + union_x2 = MAX (uta1->x0 + uta1->width, uta2->x0 + uta2->width); + union_y2 = MAX (uta1->y0 + uta1->height, uta2->y0 + uta2->height); + } + } + + /* Clip the union of the bounds */ + + new_x1 = MAX (clip_x1, union_x1); + new_y1 = MAX (clip_y1, union_y1); + new_x2 = MIN (clip_x2, union_x2); + new_y2 = MIN (clip_y2, union_y2); + + if (new_x1 >= new_x2 || new_y1 >= new_y2) + return art_uta_new (clip_x1, clip_y1, clip_x1 + 1, clip_y1 + 1); + + /* Make the new clipped union */ + + uta = art_new (ArtUta, 1); + uta->x0 = new_x1; + uta->y0 = new_y1; + uta->width = new_x2 - new_x1; + uta->height = new_y2 - new_y1; + uta->utiles = utiles = art_new (ArtUtaBbox, uta->width * uta->height); + + ofs = 0; + ofs1 = ofs2 = 0; + + for (y = new_y1; y < new_y2; y++) { + if (uta1) + ofs1 = (y - uta1->y0) * uta1->width + new_x1 - uta1->x0; + + if (uta2) + ofs2 = (y - uta2->y0) * uta2->width + new_x1 - uta2->x0; + + for (x = new_x1; x < new_x2; x++) { + ArtUtaBbox bb1, bb2, bb; + + if (!uta1 + || x < uta1->x0 || y < uta1->y0 + || x >= uta1->x0 + uta1->width || y >= uta1->y0 + uta1->height) + bb1 = 0; + else + bb1 = uta1->utiles[ofs1]; + + if (!uta2 + || x < uta2->x0 || y < uta2->y0 + || x >= uta2->x0 + uta2->width || y >= uta2->y0 + uta2->height) + bb2 = 0; + else + bb2 = uta2->utiles[ofs2]; + + if (bb1 == 0) + bb = bb2; + else if (bb2 == 0) + bb = bb1; + else + bb = ART_UTA_BBOX_CONS (MIN (ART_UTA_BBOX_X0 (bb1), + ART_UTA_BBOX_X0 (bb2)), + MIN (ART_UTA_BBOX_Y0 (bb1), + ART_UTA_BBOX_Y0 (bb2)), + MAX (ART_UTA_BBOX_X1 (bb1), + ART_UTA_BBOX_X1 (bb2)), + MAX (ART_UTA_BBOX_Y1 (bb1), + ART_UTA_BBOX_Y1 (bb2))); + + utiles[ofs] = bb; + + ofs++; + ofs1++; + ofs2++; + } + } + + return uta; +} + +static inline void +get_visible_region (GnomeCanvas *canvas, ArtIRect *visible) +{ + visible->x0 = canvas->layout.hadjustment->value - canvas->zoom_xofs; + visible->y0 = canvas->layout.vadjustment->value - canvas->zoom_yofs; + visible->x1 = visible->x0 + GTK_WIDGET (canvas)->allocation.width; + visible->y1 = visible->y0 + GTK_WIDGET (canvas)->allocation.height; +} + +/** + * gnome_canvas_request_redraw_uta: + * @canvas: A canvas. + * @uta: Microtile array that specifies the area to be redrawn. It will + * be freed by this function, so the argument you pass will be invalid + * after you call this function. + * + * Informs a canvas that the specified area, given as a microtile array, needs + * to be repainted. To be used only by item implementations. + **/ +void +gnome_canvas_request_redraw_uta (GnomeCanvas *canvas, + ArtUta *uta) +{ + ArtIRect visible; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + g_return_if_fail (uta != NULL); + + if (!gtk_widget_is_drawable (GTK_WIDGET (canvas))) { + art_uta_free (uta); + return; + } + + get_visible_region (canvas, &visible); + + if (canvas->need_redraw) { + ArtUta *new_uta; + + g_assert (canvas->redraw_area != NULL); + /* ALEX: This can fail if e.g. redraw_uta is called by an item + update function and we're called from update_now -> do_update + because update_now sets idle_id == 0. There is also some way + to get it from the expose handler (see bug #102811). + g_assert (canvas->idle_id != 0); */ + + new_uta = uta_union_clip (canvas->redraw_area, uta, &visible); + art_uta_free (canvas->redraw_area); + art_uta_free (uta); + canvas->redraw_area = new_uta; + if (canvas->idle_id == 0) + add_idle (canvas); + } else { + ArtUta *new_uta; + + g_assert (canvas->redraw_area == NULL); + + new_uta = uta_union_clip (uta, NULL, &visible); + art_uta_free (uta); + canvas->redraw_area = new_uta; + + canvas->need_redraw = TRUE; + add_idle (canvas); + } +} + + +/** + * gnome_canvas_request_redraw: + * @canvas: A canvas. + * @x1: Leftmost coordinate of the rectangle to be redrawn. + * @y1: Upper coordinate of the rectangle to be redrawn. + * @x2: Rightmost coordinate of the rectangle to be redrawn, plus 1. + * @y2: Lower coordinate of the rectangle to be redrawn, plus 1. + * + * Convenience function that informs a canvas that the specified rectangle needs + * to be repainted. This function converts the rectangle to a microtile array + * and feeds it to gnome_canvas_request_redraw_uta(). The rectangle includes + * @x1 and @y1, but not @x2 and @y2. To be used only by item implementations. + **/ +void +gnome_canvas_request_redraw (GnomeCanvas *canvas, int x1, int y1, int x2, int y2) +{ + ArtUta *uta; + ArtIRect bbox; + ArtIRect visible; + ArtIRect clip; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (!gtk_widget_is_drawable (GTK_WIDGET (canvas)) || (x1 >= x2) || (y1 >= y2)) + return; + + bbox.x0 = x1; + bbox.y0 = y1; + bbox.x1 = x2; + bbox.y1 = y2; + + get_visible_region (canvas, &visible); + + art_irect_intersect (&clip, &bbox, &visible); + + if (!art_irect_empty (&clip)) { + uta = art_uta_from_irect (&clip); + gnome_canvas_request_redraw_uta (canvas, uta); + } +} + + +/** + * gnome_canvas_w2c_affine: + * @canvas: A canvas. + * @affine: An affine transformation matrix (return value). + * + * Gets the affine transform that converts from world coordinates to canvas + * pixel coordinates. + **/ +void +gnome_canvas_w2c_affine (GnomeCanvas *canvas, double affine[6]) +{ + double zooom; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + g_return_if_fail (affine != NULL); + + zooom = canvas->pixels_per_unit; + + affine[0] = zooom; + affine[1] = 0; + affine[2] = 0; + affine[3] = zooom; + affine[4] = -canvas->scroll_x1 * zooom; + affine[5] = -canvas->scroll_y1 * zooom; +} + +/** + * gnome_canvas_w2c: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. + **/ +void +gnome_canvas_w2c (GnomeCanvas *canvas, double wx, double wy, int *cx, int *cy) +{ + double affine[6]; + ArtPoint w, c; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + gnome_canvas_w2c_affine (canvas, affine); + w.x = wx; + w.y = wy; + art_affine_point (&c, &w, affine); + if (cx) + *cx = floor (c.x + 0.5); + if (cy) + *cy = floor (c.y + 0.5); +} + +/** + * gnome_canvas_w2c_d: + * @canvas: A canvas. + * @wx: World X coordinate. + * @wy: World Y coordinate. + * @cx: X pixel coordinate (return value). + * @cy: Y pixel coordinate (return value). + * + * Converts world coordinates into canvas pixel coordinates. This + * version returns coordinates in floating point coordinates, for + * greater precision. + **/ +void +gnome_canvas_w2c_d (GnomeCanvas *canvas, double wx, double wy, double *cx, double *cy) +{ + double affine[6]; + ArtPoint w, c; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + gnome_canvas_w2c_affine (canvas, affine); + w.x = wx; + w.y = wy; + art_affine_point (&c, &w, affine); + if (cx) + *cx = c.x; + if (cy) + *cy = c.y; +} + + +/** + * gnome_canvas_c2w: + * @canvas: A canvas. + * @cx: Canvas pixel X coordinate. + * @cy: Canvas pixel Y coordinate. + * @wx: X world coordinate (return value). + * @wy: Y world coordinate (return value). + * + * Converts canvas pixel coordinates to world coordinates. + **/ +void +gnome_canvas_c2w (GnomeCanvas *canvas, int cx, int cy, double *wx, double *wy) +{ + double affine[6], inv[6]; + ArtPoint w, c; + + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + gnome_canvas_w2c_affine (canvas, affine); + art_affine_invert (inv, affine); + c.x = cx; + c.y = cy; + art_affine_point (&w, &c, inv); + if (wx) + *wx = w.x; + if (wy) + *wy = w.y; +} + + +/** + * gnome_canvas_window_to_world: + * @canvas: A canvas. + * @winx: Window-relative X coordinate. + * @winy: Window-relative Y coordinate. + * @worldx: X world coordinate (return value). + * @worldy: Y world coordinate (return value). + * + * Converts window-relative coordinates into world coordinates. You can use + * this when you need to convert mouse coordinates into world coordinates, for + * example. + **/ +void +gnome_canvas_window_to_world (GnomeCanvas *canvas, double winx, double winy, + double *worldx, double *worldy) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (worldx) + *worldx = canvas->scroll_x1 + ((winx - canvas->zoom_xofs) + / canvas->pixels_per_unit); + + if (worldy) + *worldy = canvas->scroll_y1 + ((winy - canvas->zoom_yofs) + / canvas->pixels_per_unit); +} + + +/** + * gnome_canvas_world_to_window: + * @canvas: A canvas. + * @worldx: World X coordinate. + * @worldy: World Y coordinate. + * @winx: X window-relative coordinate. + * @winy: Y window-relative coordinate. + * + * Converts world coordinates into window-relative coordinates. + **/ +void +gnome_canvas_world_to_window (GnomeCanvas *canvas, double worldx, double worldy, + double *winx, double *winy) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + if (winx) + *winx = (canvas->pixels_per_unit)*(worldx - canvas->scroll_x1) + canvas->zoom_xofs; + + if (winy) + *winy = (canvas->pixels_per_unit)*(worldy - canvas->scroll_y1) + canvas->zoom_yofs; +} + + + +/** + * gnome_canvas_get_color: + * @canvas: A canvas. + * @spec: X color specification, or NULL for "transparent". + * @color: Returns the allocated color. + * + * Allocates a color based on the specified X color specification. As a + * convenience to item implementations, it returns TRUE if the color was + * allocated, or FALSE if the specification was NULL. A NULL color + * specification is considered as "transparent" by the canvas. + * + * Return value: TRUE if @spec is non-NULL and the color is allocated. If @spec + * is NULL, then returns FALSE. + **/ +int +gnome_canvas_get_color (GnomeCanvas *canvas, const char *spec, GdkColor *color) +{ + GdkColormap *colormap; + + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), FALSE); + g_return_val_if_fail (color != NULL, FALSE); + + if (!spec) { + color->pixel = 0; + color->red = 0; + color->green = 0; + color->blue = 0; + return FALSE; + } + + gdk_color_parse (spec, color); + + colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas)); + + gdk_rgb_find_color (colormap, color); + + return TRUE; +} + +/** + * gnome_canvas_get_color_pixel: + * @canvas: A canvas. + * @rgba: RGBA color specification. + * + * Allocates a color from the RGBA value passed into this function. The alpha + * opacity value is discarded, since normal X colors do not support it. + * + * Return value: Allocated pixel value corresponding to the specified color. + **/ +gulong +gnome_canvas_get_color_pixel (GnomeCanvas *canvas, guint rgba) +{ + GdkColormap *colormap; + GdkColor color; + + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), 0); + + color.red = ((rgba & 0xff000000) >> 16) + ((rgba & 0xff000000) >> 24); + color.green = ((rgba & 0x00ff0000) >> 8) + ((rgba & 0x00ff0000) >> 16); + color.blue = (rgba & 0x0000ff00) + ((rgba & 0x0000ff00) >> 8); + color.pixel = 0; + + colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas)); + + gdk_rgb_find_color (colormap, &color); + + return color.pixel; +} + + +/** + * gnome_canvas_set_stipple_origin: + * @canvas: A canvas. + * @gc: GC on which to set the stipple origin. + * + * Sets the stipple origin of the specified GC as is appropriate for the canvas, + * so that it will be aligned with other stipple patterns used by canvas items. + * This is typically only needed by item implementations. + **/ +void +gnome_canvas_set_stipple_origin (GnomeCanvas *canvas, GdkGC *gc) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + g_return_if_fail (GDK_IS_GC (gc)); + + gdk_gc_set_ts_origin (gc, -canvas->draw_xofs, -canvas->draw_yofs); +} + +/** + * gnome_canvas_set_dither: + * @canvas: A canvas. + * @dither: Type of dithering used to render an antialiased canvas. + * + * Controls dithered rendering for antialiased canvases. The value of + * dither should be #GDK_RGB_DITHER_NONE, #GDK_RGB_DITHER_NORMAL, or + * #GDK_RGB_DITHER_MAX. The default canvas setting is + * #GDK_RGB_DITHER_NORMAL. + **/ +void +gnome_canvas_set_dither (GnomeCanvas *canvas, GdkRgbDither dither) +{ + g_return_if_fail (GNOME_IS_CANVAS (canvas)); + + canvas->dither = dither; +} + +/** + * gnome_canvas_get_dither: + * @canvas: A canvas. + * + * Returns the type of dithering used to render an antialiased canvas. + * + * Return value: The dither setting. + **/ +GdkRgbDither +gnome_canvas_get_dither (GnomeCanvas *canvas) +{ + g_return_val_if_fail (GNOME_IS_CANVAS (canvas), GDK_RGB_DITHER_NONE); + + return canvas->dither; +} + +static gboolean +boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +/* Class initialization function for GnomeCanvasItemClass */ +static void +gnome_canvas_item_class_init (GnomeCanvasItemClass *class) +{ + GObjectClass *gobject_class; + + gobject_class = (GObjectClass *) class; + + item_parent_class = g_type_class_peek_parent (class); + + gobject_class->set_property = gnome_canvas_item_set_property; + gobject_class->get_property = gnome_canvas_item_get_property; + + g_object_class_install_property + (gobject_class, ITEM_PROP_PARENT, + g_param_spec_object ("parent", NULL, NULL, + GNOME_TYPE_CANVAS_ITEM, + (G_PARAM_READABLE | G_PARAM_WRITABLE))); + + item_signals[ITEM_EVENT] = + g_signal_new ("event", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GnomeCanvasItemClass, event), + boolean_handled_accumulator, NULL, + gnome_canvas_marshal_BOOLEAN__BOXED, + G_TYPE_BOOLEAN, 1, + GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE); + + gobject_class->dispose = gnome_canvas_item_dispose; + + class->realize = gnome_canvas_item_realize; + class->unrealize = gnome_canvas_item_unrealize; + class->map = gnome_canvas_item_map; + class->unmap = gnome_canvas_item_unmap; + class->update = gnome_canvas_item_update; +} diff --git a/libgnomecanvas/gnome-canvas.h b/libgnomecanvas/gnome-canvas.h new file mode 100644 index 0000000000..a9c70be4f5 --- /dev/null +++ b/libgnomecanvas/gnome-canvas.h @@ -0,0 +1,635 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: 8; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ +/* GnomeCanvas widget - Tk-like canvas widget for Gnome + * + * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas + * widget. Tk is copyrighted by the Regents of the University of California, + * Sun Microsystems, and other parties. + * + * + * Authors: Federico Mena <federico@nuclecu.unam.mx> + * Raph Levien <raph@gimp.org> + */ + +#ifndef GNOME_CANVAS_H +#define GNOME_CANVAS_H + +#include <gtk/gtk.h> +#include <stdarg.h> +#include <libart_lgpl/art_misc.h> +#include <libart_lgpl/art_rect.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_uta.h> +#include <libart_lgpl/art_affine.h> + +G_BEGIN_DECLS + + +/* "Small" value used by canvas stuff */ +#define GNOME_CANVAS_EPSILON 1e-10 + + +/* Macros for building colors that fit in a 32-bit integer. The values are in + * [0, 255]. + */ + +#define GNOME_CANVAS_COLOR(r, g, b) ((((unsigned int) (r) & 0xff) << 24) \ + | (((unsigned int) (g) & 0xff) << 16) \ + | (((unsigned int) (b) & 0xff) << 8) \ + | 0xff) + +#define GNOME_CANVAS_COLOR_A(r, g, b, a) ((((unsigned int) (r) & 0xff) << 24) \ + | (((unsigned int) (g) & 0xff) << 16) \ + | (((unsigned int) (b) & 0xff) << 8) \ + | ((unsigned int) (a) & 0xff)) + + +typedef struct _GnomeCanvas GnomeCanvas; +typedef struct _GnomeCanvasClass GnomeCanvasClass; +typedef struct _GnomeCanvasItem GnomeCanvasItem; +typedef struct _GnomeCanvasItemClass GnomeCanvasItemClass; +typedef struct _GnomeCanvasGroup GnomeCanvasGroup; +typedef struct _GnomeCanvasGroupClass GnomeCanvasGroupClass; + + +/* GnomeCanvasItem - base item class for canvas items + * + * All canvas items are derived from GnomeCanvasItem. The only information a + * GnomeCanvasItem contains is its parent canvas, its parent canvas item group, + * its bounding box in world coordinates, and its current affine transformation. + * + * Items inside a canvas are organized in a tree of GnomeCanvasItemGroup nodes + * and GnomeCanvasItem leaves. Each canvas has a single root group, which can + * be obtained with the gnome_canvas_get_root() function. + * + * The abstract GnomeCanvasItem class does not have any configurable or + * queryable attributes. + */ + +/* Object flags for items */ +enum { + GNOME_CANVAS_ITEM_REALIZED = 1 << 4, + GNOME_CANVAS_ITEM_MAPPED = 1 << 5, + GNOME_CANVAS_ITEM_ALWAYS_REDRAW = 1 << 6, + GNOME_CANVAS_ITEM_VISIBLE = 1 << 7, + GNOME_CANVAS_ITEM_NEED_UPDATE = 1 << 8, + GNOME_CANVAS_ITEM_NEED_AFFINE = 1 << 9, + GNOME_CANVAS_ITEM_NEED_CLIP = 1 << 10, + GNOME_CANVAS_ITEM_NEED_VIS = 1 << 11, + GNOME_CANVAS_ITEM_AFFINE_FULL = 1 << 12 +}; + +/* Update flags for items */ +enum { + GNOME_CANVAS_UPDATE_REQUESTED = 1 << 0, + GNOME_CANVAS_UPDATE_AFFINE = 1 << 1, + GNOME_CANVAS_UPDATE_CLIP = 1 << 2, + GNOME_CANVAS_UPDATE_VISIBILITY = 1 << 3, + GNOME_CANVAS_UPDATE_IS_VISIBLE = 1 << 4 /* Deprecated. FIXME: remove this */ +}; + +/* Data for rendering in antialiased mode */ +typedef struct { + /* 24-bit RGB buffer for rendering */ + guchar *buf; + + /* Rectangle describing the rendering area */ + ArtIRect rect; + + /* Rowstride for the buffer */ + int buf_rowstride; + + /* Background color, given as 0xrrggbb */ + guint32 bg_color; + + /* Invariant: at least one of the following flags is true. */ + + /* Set when the render rectangle area is the solid color bg_color */ + unsigned int is_bg : 1; + + /* Set when the render rectangle area is represented by the buf */ + unsigned int is_buf : 1; +} GnomeCanvasBuf; + + +#define GNOME_TYPE_CANVAS_ITEM (gnome_canvas_item_get_type ()) +#define GNOME_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_ITEM, GnomeCanvasItem)) +#define GNOME_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_ITEM, GnomeCanvasItemClass)) +#define GNOME_IS_CANVAS_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_ITEM)) +#define GNOME_IS_CANVAS_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_ITEM)) +#define GNOME_CANVAS_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_ITEM, GnomeCanvasItemClass)) + + +struct _GnomeCanvasItem { + GtkObject object; + + /* Parent canvas for this item */ + GnomeCanvas *canvas; + + /* Parent canvas group for this item (a GnomeCanvasGroup) */ + GnomeCanvasItem *parent; + + /* If NULL, assumed to be the identity tranform. If flags does not have + * AFFINE_FULL, then a two-element array containing a translation. If + * flags contains AFFINE_FULL, a six-element array containing an affine + * transformation. + */ + double *xform; + + /* Bounding box for this item (in canvas coordinates) */ + double x1, y1, x2, y2; +}; + +struct _GnomeCanvasItemClass { + GtkObjectClass parent_class; + + /* Tell the item to update itself. The flags are from the update flags + * defined above. The item should update its internal state from its + * queued state, and recompute and request its repaint area. The + * affine, if used, is a pointer to a 6-element array of doubles. The + * update method also recomputes the bounding box of the item. + */ + void (* update) (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); + + /* Realize an item -- create GCs, etc. */ + void (* realize) (GnomeCanvasItem *item); + + /* Unrealize an item */ + void (* unrealize) (GnomeCanvasItem *item); + + /* Map an item - normally only need by items with their own GdkWindows */ + void (* map) (GnomeCanvasItem *item); + + /* Unmap an item */ + void (* unmap) (GnomeCanvasItem *item); + + /* Return the microtile coverage of the item */ + ArtUta *(* coverage) (GnomeCanvasItem *item); + + /* Draw an item of this type. (x, y) are the upper-left canvas pixel + * coordinates of the drawable, a temporary pixmap, where things get + * drawn. (width, height) are the dimensions of the drawable. + */ + void (* draw) (GnomeCanvasItem *item, GdkDrawable *drawable, + int x, int y, int width, int height); + + /* Render the item over the buffer given. The buf data structure + * contains both a pointer to a packed 24-bit RGB array, and the + * coordinates. This method is only used for antialiased canvases. + * + * TODO: figure out where clip paths fit into the rendering framework. + */ + void (* render) (GnomeCanvasItem *item, GnomeCanvasBuf *buf); + + /* Calculate the distance from an item to the specified point. It also + * returns a canvas item which is the item itself in the case of the + * object being an actual leaf item, or a child in case of the object + * being a canvas group. (cx, cy) are the canvas pixel coordinates that + * correspond to the item-relative coordinates (x, y). + */ + double (* point) (GnomeCanvasItem *item, double x, double y, int cx, int cy, + GnomeCanvasItem **actual_item); + + /* Fetch the item's bounding box (need not be exactly tight). This + * should be in item-relative coordinates. + */ + void (* bounds) (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); + + /* Signal: an event occurred for an item of this type. The (x, y) + * coordinates are in the canvas world coordinate system. + */ + gboolean (* event) (GnomeCanvasItem *item, GdkEvent *event); + + /* Reserved for future expansion */ + gpointer spare_vmethods [4]; +}; + + +GType gnome_canvas_item_get_type (void) G_GNUC_CONST; + +/* Create a canvas item using the standard Gtk argument mechanism. The item is + * automatically inserted at the top of the specified canvas group. The last + * argument must be a NULL pointer. + */ +GnomeCanvasItem *gnome_canvas_item_new (GnomeCanvasGroup *parent, GType type, + const gchar *first_arg_name, ...); + +/* Constructors for use in derived classes and language wrappers */ +void gnome_canvas_item_construct (GnomeCanvasItem *item, GnomeCanvasGroup *parent, + const gchar *first_arg_name, va_list args); + +/* Configure an item using the standard Gtk argument mechanism. The last + * argument must be a NULL pointer. + */ +void gnome_canvas_item_set (GnomeCanvasItem *item, const gchar *first_arg_name, ...); + +/* Used only for language wrappers and the like */ +void gnome_canvas_item_set_valist (GnomeCanvasItem *item, + const gchar *first_arg_name, va_list args); + +/* Move an item by the specified amount */ +void gnome_canvas_item_move (GnomeCanvasItem *item, double dx, double dy); + +/* Apply a relative affine transformation to the item. */ +void gnome_canvas_item_affine_relative (GnomeCanvasItem *item, const double affine[6]); + +/* Apply an absolute affine transformation to the item. */ +void gnome_canvas_item_affine_absolute (GnomeCanvasItem *item, const double affine[6]); + +/* Raise an item in the z-order of its parent group by the specified number of + * positions. + */ +void gnome_canvas_item_raise (GnomeCanvasItem *item, int positions); + +/* Lower an item in the z-order of its parent group by the specified number of + * positions. + */ +void gnome_canvas_item_lower (GnomeCanvasItem *item, int positions); + +/* Raise an item to the top of its parent group's z-order. */ +void gnome_canvas_item_raise_to_top (GnomeCanvasItem *item); + +/* Lower an item to the bottom of its parent group's z-order */ +void gnome_canvas_item_lower_to_bottom (GnomeCanvasItem *item); + +/* Show an item (make it visible). If the item is already shown, it has no + * effect. + */ +void gnome_canvas_item_show (GnomeCanvasItem *item); + +/* Hide an item (make it invisible). If the item is already invisible, it has + * no effect. + */ +void gnome_canvas_item_hide (GnomeCanvasItem *item); + +/* Grab the mouse for the specified item. Only the events in event_mask will be + * reported. If cursor is non-NULL, it will be used during the duration of the + * grab. Time is a proper X event time parameter. Returns the same values as + * XGrabPointer(). + */ +int gnome_canvas_item_grab (GnomeCanvasItem *item, unsigned int event_mask, + GdkCursor *cursor, guint32 etime); + +/* Ungrabs the mouse -- the specified item must be the same that was passed to + * gnome_canvas_item_grab(). Time is a proper X event time parameter. + */ +void gnome_canvas_item_ungrab (GnomeCanvasItem *item, guint32 etime); + +/* These functions convert from a coordinate system to another. "w" is world + * coordinates and "i" is item coordinates. + */ +void gnome_canvas_item_w2i (GnomeCanvasItem *item, double *x, double *y); +void gnome_canvas_item_i2w (GnomeCanvasItem *item, double *x, double *y); + +/* Gets the affine transform that converts from item-relative coordinates to + * world coordinates. + */ +void gnome_canvas_item_i2w_affine (GnomeCanvasItem *item, double affine[6]); + +/* Gets the affine transform that converts from item-relative coordinates to + * canvas pixel coordinates. + */ +void gnome_canvas_item_i2c_affine (GnomeCanvasItem *item, double affine[6]); + +/* Remove the item from its parent group and make the new group its parent. The + * item will be put on top of all the items in the new group. The item's + * coordinates relative to its new parent to *not* change -- this means that the + * item could potentially move on the screen. + * + * The item and the group must be in the same canvas. An item cannot be + * reparented to a group that is the item itself or that is an inferior of the + * item. + */ +void gnome_canvas_item_reparent (GnomeCanvasItem *item, GnomeCanvasGroup *new_group); + +/* Used to send all of the keystroke events to a specific item as well as + * GDK_FOCUS_CHANGE events. + */ +void gnome_canvas_item_grab_focus (GnomeCanvasItem *item); + +/* Fetch the bounding box of the item. The bounding box may not be exactly + * tight, but the canvas items will do the best they can. The returned bounding + * box is in the coordinate system of the item's parent. + */ +void gnome_canvas_item_get_bounds (GnomeCanvasItem *item, + double *x1, double *y1, double *x2, double *y2); + +/* Request that the update method eventually get called. This should be used + * only by item implementations. + */ +void gnome_canvas_item_request_update (GnomeCanvasItem *item); + + +/* GnomeCanvasGroup - a group of canvas items + * + * A group is a node in the hierarchical tree of groups/items inside a canvas. + * Groups serve to give a logical structure to the items. + * + * Consider a circuit editor application that uses the canvas for its schematic + * display. Hierarchically, there would be canvas groups that contain all the + * components needed for an "adder", for example -- this includes some logic + * gates as well as wires. You can move stuff around in a convenient way by + * doing a gnome_canvas_item_move() of the hierarchical groups -- to move an + * adder, simply move the group that represents the adder. + * + * The following arguments are available: + * + * name type read/write description + * -------------------------------------------------------------------------------- + * x double RW X coordinate of group's origin + * y double RW Y coordinate of group's origin + */ + + +#define GNOME_TYPE_CANVAS_GROUP (gnome_canvas_group_get_type ()) +#define GNOME_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS_GROUP, GnomeCanvasGroup)) +#define GNOME_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS_GROUP, GnomeCanvasGroupClass)) +#define GNOME_IS_CANVAS_GROUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS_GROUP)) +#define GNOME_IS_CANVAS_GROUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS_GROUP)) +#define GNOME_CANVAS_GROUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS_GROUP, GnomeCanvasGroupClass)) + + +struct _GnomeCanvasGroup { + GnomeCanvasItem item; + + /* Children of the group */ + GList *item_list; + GList *item_list_end; +}; + +struct _GnomeCanvasGroupClass { + GnomeCanvasItemClass parent_class; +}; + + +GType gnome_canvas_group_get_type (void) G_GNUC_CONST; + + +/*** GnomeCanvas ***/ + + +#define GNOME_TYPE_CANVAS (gnome_canvas_get_type ()) +#define GNOME_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNOME_TYPE_CANVAS, GnomeCanvas)) +#define GNOME_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNOME_TYPE_CANVAS, GnomeCanvasClass)) +#define GNOME_IS_CANVAS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNOME_TYPE_CANVAS)) +#define GNOME_IS_CANVAS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNOME_TYPE_CANVAS)) +#define GNOME_CANVAS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNOME_TYPE_CANVAS, GnomeCanvasClass)) + + +struct _GnomeCanvas { + GtkLayout layout; + + /* Root canvas group */ + GnomeCanvasItem *root; + + /* Area that needs redrawing, stored as a microtile array */ + ArtUta *redraw_area; + + /* The item containing the mouse pointer, or NULL if none */ + GnomeCanvasItem *current_item; + + /* Item that is about to become current (used to track deletions and such) */ + GnomeCanvasItem *new_current_item; + + /* Item that holds a pointer grab, or NULL if none */ + GnomeCanvasItem *grabbed_item; + + /* If non-NULL, the currently focused item */ + GnomeCanvasItem *focused_item; + + /* GC for temporary draw pixmap */ + GdkGC *pixmap_gc; + + /* Event on which selection of current item is based */ + GdkEvent pick_event; + + /* Scrolling region */ + double scroll_x1, scroll_y1; + double scroll_x2, scroll_y2; + + /* Scaling factor to be used for display */ + double pixels_per_unit; + + /* Idle handler ID */ + guint idle_id; + + /* Signal handler ID for destruction of the root item */ + guint root_destroy_id; + + /* Area that is being redrawn. Contains (x1, y1) but not (x2, y2). + * Specified in canvas pixel coordinates. + */ + int redraw_x1, redraw_y1; + int redraw_x2, redraw_y2; + + /* Offsets of the temprary drawing pixmap */ + int draw_xofs, draw_yofs; + + /* Internal pixel offsets when zoomed out */ + int zoom_xofs, zoom_yofs; + + /* Last known modifier state, for deferred repick when a button is down */ + int state; + + /* Event mask specified when grabbing an item */ + guint grabbed_event_mask; + + /* Tolerance distance for picking items */ + int close_enough; + + /* Whether the canvas should center the scroll region in the middle of + * the window if the scroll region is smaller than the window. + */ + unsigned int center_scroll_region : 1; + + /* Whether items need update at next idle loop iteration */ + unsigned int need_update : 1; + + /* Whether the canvas needs redrawing at the next idle loop iteration */ + unsigned int need_redraw : 1; + + /* Whether current item will be repicked at next idle loop iteration */ + unsigned int need_repick : 1; + + /* For use by internal pick_current_item() function */ + unsigned int left_grabbed_item : 1; + + /* For use by internal pick_current_item() function */ + unsigned int in_repick : 1; + + /* Whether the canvas is in antialiased mode or not */ + unsigned int aa : 1; + + /* Which dither mode to use for antialiased mode drawing */ + GdkRgbDither dither; +}; + +struct _GnomeCanvasClass { + GtkLayoutClass parent_class; + + /* Draw the background for the area given. This method is only used + * for non-antialiased canvases. + */ + void (* draw_background) (GnomeCanvas *canvas, GdkDrawable *drawable, + int x, int y, int width, int height); + + /* Render the background for the buffer given. The buf data structure + * contains both a pointer to a packed 24-bit RGB array, and the + * coordinates. This method is only used for antialiased canvases. + */ + void (* render_background) (GnomeCanvas *canvas, GnomeCanvasBuf *buf); + + /* Private Virtual methods for groping the canvas inside bonobo */ + void (* request_update) (GnomeCanvas *canvas); + + /* Reserved for future expansion */ + gpointer spare_vmethods [4]; +}; + + +GType gnome_canvas_get_type (void) G_GNUC_CONST; + +/* Creates a new canvas. You should check that the canvas is created with the + * proper visual and colormap. Any visual will do unless you intend to insert + * gdk_imlib images into it, in which case you should use the gdk_imlib visual. + * + * You should call gnome_canvas_set_scroll_region() soon after calling this + * function to set the desired scrolling limits for the canvas. + */ +GtkWidget *gnome_canvas_new (void); + +/* Creates a new antialiased empty canvas. You should push the GdkRgb colormap + * and visual for this. + */ +#ifndef GNOME_EXCLUDE_EXPERIMENTAL +GtkWidget *gnome_canvas_new_aa (void); +#endif + +/* Returns the root canvas item group of the canvas */ +GnomeCanvasGroup *gnome_canvas_root (GnomeCanvas *canvas); + +/* Sets the limits of the scrolling region, in world coordinates */ +void gnome_canvas_set_scroll_region (GnomeCanvas *canvas, + double x1, double y1, double x2, double y2); + +/* Gets the limits of the scrolling region, in world coordinates */ +void gnome_canvas_get_scroll_region (GnomeCanvas *canvas, + double *x1, double *y1, double *x2, double *y2); + +/* Whether the canvas centers the scroll region if it is smaller than the window */ +void gnome_canvas_set_center_scroll_region (GnomeCanvas *canvas, gboolean center_scroll_region); + +/* Returns whether the canvas is set to center the scroll region if it is smaller than the window */ +gboolean gnome_canvas_get_center_scroll_region (GnomeCanvas *canvas); + +/* Sets the number of pixels that correspond to one unit in world coordinates */ +void gnome_canvas_set_pixels_per_unit (GnomeCanvas *canvas, double n); + +/* Scrolls the canvas to the specified offsets, given in canvas pixel coordinates */ +void gnome_canvas_scroll_to (GnomeCanvas *canvas, int cx, int cy); + +/* Returns the scroll offsets of the canvas in canvas pixel coordinates. You + * can specify NULL for any of the values, in which case that value will not be + * queried. + */ +void gnome_canvas_get_scroll_offsets (GnomeCanvas *canvas, int *cx, int *cy); + +/* Requests that the canvas be repainted immediately instead of in the idle + * loop. + */ +void gnome_canvas_update_now (GnomeCanvas *canvas); + +/* Returns the item that is at the specified position in world coordinates, or + * NULL if no item is there. + */ +GnomeCanvasItem *gnome_canvas_get_item_at (GnomeCanvas *canvas, double x, double y); + +/* For use only by item type implementations. Request that the canvas eventually + * redraw the specified region. The region is specified as a microtile + * array. This function takes over responsibility for freeing the uta argument. + */ +void gnome_canvas_request_redraw_uta (GnomeCanvas *canvas, ArtUta *uta); + +/* For use only by item type implementations. Request that the canvas + * eventually redraw the specified region, specified in canvas pixel + * coordinates. The region contains (x1, y1) but not (x2, y2). + */ +void gnome_canvas_request_redraw (GnomeCanvas *canvas, int x1, int y1, int x2, int y2); + +/* Gets the affine transform that converts world coordinates into canvas pixel + * coordinates. + */ +void gnome_canvas_w2c_affine (GnomeCanvas *canvas, double affine[6]); + +/* These functions convert from a coordinate system to another. "w" is world + * coordinates, "c" is canvas pixel coordinates (pixel coordinates that are + * (0,0) for the upper-left scrolling limit and something else for the + * lower-left scrolling limit). + */ +void gnome_canvas_w2c (GnomeCanvas *canvas, double wx, double wy, int *cx, int *cy); +void gnome_canvas_w2c_d (GnomeCanvas *canvas, double wx, double wy, double *cx, double *cy); +void gnome_canvas_c2w (GnomeCanvas *canvas, int cx, int cy, double *wx, double *wy); + +/* This function takes in coordinates relative to the GTK_LAYOUT + * (canvas)->bin_window and converts them to world coordinates. + */ +void gnome_canvas_window_to_world (GnomeCanvas *canvas, + double winx, double winy, double *worldx, double *worldy); + +/* This is the inverse of gnome_canvas_window_to_world() */ +void gnome_canvas_world_to_window (GnomeCanvas *canvas, + double worldx, double worldy, double *winx, double *winy); + +/* Takes a string specification for a color and allocates it into the specified + * GdkColor. If the string is null, then it returns FALSE. Otherwise, it + * returns TRUE. + */ +int gnome_canvas_get_color (GnomeCanvas *canvas, const char *spec, GdkColor *color); + +/* Allocates a color from the RGB value passed into this function. */ +gulong gnome_canvas_get_color_pixel (GnomeCanvas *canvas, + guint rgba); + + +/* Sets the stipple origin of the specified gc so that it will be aligned with + * all the stipples used in the specified canvas. This is intended for use only + * by canvas item implementations. + */ +void gnome_canvas_set_stipple_origin (GnomeCanvas *canvas, GdkGC *gc); + +/* Controls the dithering used when the canvas renders. + * Only applicable to antialiased canvases - ignored by non-antialiased canvases. + */ +void gnome_canvas_set_dither (GnomeCanvas *canvas, GdkRgbDither dither); + +/* Returns the dither mode of an antialiased canvas. + * Only applicable to antialiased canvases - ignored by non-antialiased canvases. + */ +GdkRgbDither gnome_canvas_get_dither (GnomeCanvas *canvas); + +G_END_DECLS + +#endif diff --git a/libgnomecanvas/libgnomecanvas.h b/libgnomecanvas/libgnomecanvas.h new file mode 100644 index 0000000000..93e21fa4bd --- /dev/null +++ b/libgnomecanvas/libgnomecanvas.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ + +#ifndef LIBGNOMECANVAS_H +#define LIBGNOMECANVAS_H + +#include <libgnomecanvas/gnome-canvas.h> +#include <libgnomecanvas/gnome-canvas-line.h> +#include <libgnomecanvas/gnome-canvas-text.h> +#include <libgnomecanvas/gnome-canvas-rich-text.h> +#include <libgnomecanvas/gnome-canvas-polygon.h> +#include <libgnomecanvas/gnome-canvas-pixbuf.h> +#include <libgnomecanvas/gnome-canvas-widget.h> +#include <libgnomecanvas/gnome-canvas-rect-ellipse.h> +#include <libgnomecanvas/gnome-canvas-bpath.h> +#include <libgnomecanvas/gnome-canvas-util.h> +#include <libgnomecanvas/gnome-canvas-clipgroup.h> + +G_BEGIN_DECLS + +GType gnome_canvas_points_get_type (void); +#define GNOME_TYPE_CANVAS_POINTS gnome_canvas_points_get_type() + +G_END_DECLS + +#endif /* LIBGNOMECANVAS_H */ diff --git a/libgnomecanvas/libgnomecanvastypes.c b/libgnomecanvas/libgnomecanvastypes.c new file mode 100644 index 0000000000..9ea59664c8 --- /dev/null +++ b/libgnomecanvas/libgnomecanvastypes.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 1999, 2000 Red Hat, Inc. + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* + @NOTATION@ + */ + +#include <config.h> +#include <glib-object.h> + +#include <libgnomecanvas/libgnomecanvas.h> + +GType +gnome_canvas_points_get_type (void) +{ + static GType type_canvas_points = 0; + + if (!type_canvas_points) + type_canvas_points = g_boxed_type_register_static + ("GnomeCanvasPoints", + (GBoxedCopyFunc) gnome_canvas_points_ref, + (GBoxedFreeFunc) gnome_canvas_points_unref); + + return type_canvas_points; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 7e498d27e1..7dec6b07e3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -181,6 +181,9 @@ filter/e-rule-context.c filter/e-rule-editor.c filter/filter.error.xml [type: gettext/glade]filter/filter.ui +libgnomecanvas/gnome-canvas-rich-text.c +libgnomecanvas/gnome-canvas-text.c +libgnomecanvas/gnome-canvas.c mail/e-mail-attachment-bar.c mail/e-mail-browser.c mail/e-mail-display.c diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index 6373ceb58b..b779896890 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -156,13 +156,14 @@ libemiscwidgets_la_SOURCES = \ libemiscwidgets_la_LDFLAGS = $(NO_UNDEFINED) -libemiscwidgets_la_LIBADD = \ - $(top_builddir)/e-util/libeutil.la \ - $(top_builddir)/filter/libfilter.la \ - $(top_builddir)/a11y/libevolution-a11y.la \ - $(EVOLUTION_MAIL_LIBS) \ - $(GNOME_PLATFORM_LIBS) \ - $(MATH_LIB) \ +libemiscwidgets_la_LIBADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/filter/libfilter.la \ + $(top_builddir)/a11y/libevolution-a11y.la \ + $(top_builddir)/libgnomecanvas/libgnomecanvas-2.la \ + $(EVOLUTION_MAIL_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(MATH_LIB) \ $(ICONV_LIBS) noinst_PROGRAMS = \ |