Compare commits
82 Commits
path-point
...
path-round
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44bc5a139c | ||
|
|
11f48f3c5b | ||
|
|
fa4ad1590d | ||
|
|
47ddc742fc | ||
|
|
31d994837b | ||
|
|
83d692e2d3 | ||
|
|
f2835bb48b | ||
|
|
857c22db32 | ||
|
|
fff1645060 | ||
|
|
4f0c3c81fb | ||
|
|
421365cfc3 | ||
|
|
59c5b41f3d | ||
|
|
267367355f | ||
|
|
0aa3356499 | ||
|
|
14a3b35333 | ||
|
|
77def95659 | ||
|
|
4e91cd1155 | ||
|
|
2d923d362f | ||
|
|
b7544636d4 | ||
|
|
6220baf230 | ||
|
|
917307a6a4 | ||
|
|
5f49b7b492 | ||
|
|
b03abea402 | ||
|
|
d4fcc883cb | ||
|
|
ca2a2697d6 | ||
|
|
68c6f63e90 | ||
|
|
0e89fed8ed | ||
|
|
4d726543d0 | ||
|
|
2bcc998e90 | ||
|
|
f707185248 | ||
|
|
ded30049db | ||
|
|
5c55e9d11e | ||
|
|
c35bb11c18 | ||
|
|
cc19781d27 | ||
|
|
08d08678e8 | ||
|
|
11b632a691 | ||
|
|
4881ef251e | ||
|
|
5b39abd1a4 | ||
|
|
f0089097dd | ||
|
|
d603925ab2 | ||
|
|
6af9dc8612 | ||
|
|
35b313f9b0 | ||
|
|
fe84ce8686 | ||
|
|
02a4e5a457 | ||
|
|
a548a91e6d | ||
|
|
fc3c2e0942 | ||
|
|
60e07335a1 | ||
|
|
cb6b968fb5 | ||
|
|
3aa3816125 | ||
|
|
c37c29422a | ||
|
|
db71c07f8f | ||
|
|
bf1b97efd3 | ||
|
|
4d7277f72c | ||
|
|
3296f01edf | ||
|
|
dbcbf0523b | ||
|
|
888abb6f24 | ||
|
|
9b916b329a | ||
|
|
eb3ada6386 | ||
|
|
2a725b658b | ||
|
|
97f605f811 | ||
|
|
4e7d578881 | ||
|
|
438d936f57 | ||
|
|
fc4a464b47 | ||
|
|
785b9541f6 | ||
|
|
a4cbabb80f | ||
|
|
1b5dfcba7e | ||
|
|
ba41edf531 | ||
|
|
bf3892caed | ||
|
|
1e6a95aa1b | ||
|
|
70a9d08e21 | ||
|
|
7511d4e281 | ||
|
|
53ea97decb | ||
|
|
28190ded71 | ||
|
|
8083cb0e47 | ||
|
|
544320a961 | ||
|
|
ee5a95ba24 | ||
|
|
232260b86b | ||
|
|
857f50649c | ||
|
|
a483fb2d96 | ||
|
|
d27e7e0180 | ||
|
|
aa888c0b3f | ||
|
|
5240d8cdf5 |
@@ -335,6 +335,7 @@
|
||||
<file>paintable_symbolic.c</file>
|
||||
<file>panes.c</file>
|
||||
<file>password_entry.c</file>
|
||||
<file>path_fill.c</file>
|
||||
<file>peg_solitaire.c</file>
|
||||
<file>pickers.c</file>
|
||||
<file>printing.c</file>
|
||||
|
||||
@@ -72,6 +72,7 @@ demos = files([
|
||||
'paintable_symbolic.c',
|
||||
'panes.c',
|
||||
'password_entry.c',
|
||||
'path_fill.c',
|
||||
'peg_solitaire.c',
|
||||
'pickers.c',
|
||||
'printing.c',
|
||||
|
||||
198
demos/gtk-demo/path_fill.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/* Path/Fill and Stroke
|
||||
*
|
||||
* This demo shows how to use GskPath to draw shapes that are (a bit)
|
||||
* more complex than a rounded rectangle.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
#define GTK_TYPE_LOGO_PAINTABLE (gtk_logo_paintable_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkLogoPaintable, gtk_logo_paintable, GTK, LOGO_PAINTABLE, GObject)
|
||||
|
||||
struct _GtkLogoPaintable
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
GskPath *path[3];
|
||||
GdkRGBA color[3];
|
||||
|
||||
GskPath *stroke_path;
|
||||
GskStroke *stroke1;
|
||||
GskStroke *stroke2;
|
||||
GdkRGBA stroke_color;
|
||||
};
|
||||
|
||||
struct _GtkLogoPaintableClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static int
|
||||
gtk_logo_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
|
||||
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_logo_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
|
||||
|
||||
return self->height;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_logo_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
|
||||
|
||||
for (unsigned int i = 0; i < 3; i++)
|
||||
{
|
||||
gtk_snapshot_push_fill (snapshot, self->path[i], GSK_FILL_RULE_WINDING);
|
||||
gtk_snapshot_append_color (snapshot,
|
||||
&self->color[i],
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||
gtk_snapshot_pop (snapshot);
|
||||
}
|
||||
for (unsigned int i = 0; i < 3; i++)
|
||||
{
|
||||
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke1);
|
||||
gtk_snapshot_append_color (snapshot,
|
||||
&self->stroke_color,
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||
gtk_snapshot_pop (snapshot);
|
||||
}
|
||||
|
||||
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke2);
|
||||
gtk_snapshot_append_color (snapshot,
|
||||
&self->stroke_color,
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||
gtk_snapshot_pop (snapshot);
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_logo_paintable_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_logo_paintable_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->get_intrinsic_width = gtk_logo_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_logo_paintable_get_intrinsic_height;
|
||||
iface->snapshot = gtk_logo_paintable_snapshot;
|
||||
iface->get_flags = gtk_logo_paintable_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkLogoPaintable, gtk_logo_paintable, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_logo_paintable_paintable_init))
|
||||
|
||||
static void
|
||||
gtk_logo_paintable_dispose (GObject *object)
|
||||
{
|
||||
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (object);
|
||||
|
||||
for (unsigned int i = 0; i < 3; i++)
|
||||
gsk_path_unref (self->path[i]);
|
||||
|
||||
gsk_path_unref (self->stroke_path);
|
||||
|
||||
gsk_stroke_free (self->stroke1);
|
||||
gsk_stroke_free (self->stroke2);
|
||||
|
||||
G_OBJECT_CLASS (gtk_logo_paintable_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_logo_paintable_class_init (GtkLogoPaintableClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_logo_paintable_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_logo_paintable_init (GtkLogoPaintable *self)
|
||||
{
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_logo_paintable_new (void)
|
||||
{
|
||||
GtkLogoPaintable *self;
|
||||
graphene_rect_t bounds, bounds2;
|
||||
|
||||
self = g_object_new (GTK_TYPE_LOGO_PAINTABLE, NULL);
|
||||
|
||||
/* Paths and colors extracted from gtk-logo.svg */
|
||||
self->path[0] = gsk_path_parse ("m3.12,66.17 -2.06,-51.46 32.93,24.7 v55.58 l-30.87,-28.82 z");
|
||||
self->path[1] = gsk_path_parse ("m34,95 49.4,-20.58 4.12,-51.46 -53.52,16.47 v55.58 z");
|
||||
self->path[2] = gsk_path_parse ("m1.06,14.71 32.93,24.7 53.52,-16.47 -36.75,-21.88 -49.7,13.65 z");
|
||||
|
||||
gdk_rgba_parse (&self->color[0], "#e40000");
|
||||
gdk_rgba_parse (&self->color[1], "#7fe719");
|
||||
gdk_rgba_parse (&self->color[2], "#729fcf");
|
||||
|
||||
self->stroke_path = gsk_path_parse ("m50.6,51.3 -47.3,14 z l33,23 z v-50");
|
||||
self->stroke1 = gsk_stroke_new (2.12);
|
||||
self->stroke2 = gsk_stroke_new (1.25);
|
||||
gdk_rgba_parse (&self->stroke_color, "#ffffff");
|
||||
|
||||
gsk_path_get_stroke_bounds (self->path[0], self->stroke1, &bounds);
|
||||
gsk_path_get_stroke_bounds (self->path[1], self->stroke1, &bounds2);
|
||||
graphene_rect_union (&bounds, &bounds2, &bounds);
|
||||
gsk_path_get_stroke_bounds (self->path[2], self->stroke1, &bounds2);
|
||||
graphene_rect_union (&bounds, &bounds2, &bounds);
|
||||
gsk_path_get_stroke_bounds (self->stroke_path, self->stroke2, &bounds2);
|
||||
graphene_rect_union (&bounds, &bounds2, &bounds);
|
||||
|
||||
self->width = bounds.origin.x + bounds.size.width;
|
||||
self->height = bounds.origin.y + bounds.size.height;
|
||||
|
||||
return GDK_PAINTABLE (self);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_fill (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkWidget *picture;
|
||||
GdkPaintable *paintable;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
paintable = gtk_logo_paintable_new ();
|
||||
picture = gtk_picture_new_for_paintable (paintable);
|
||||
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
|
||||
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
|
||||
g_object_unref (paintable);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), picture);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -34,5 +34,11 @@ base_url = "https://gitlab.gnome.org/GNOME/gtk/-/blob/main/"
|
||||
[extra]
|
||||
content_images = [
|
||||
"gtk-logo.svg",
|
||||
"images/caps-dark.png",
|
||||
"images/caps-light.png",
|
||||
"images/join-dark.png",
|
||||
"images/join-light.png",
|
||||
"images/path-dark.png",
|
||||
"images/path-light.png",
|
||||
]
|
||||
urlmap_file = "urlmap.js"
|
||||
|
||||
BIN
docs/reference/gsk/images/caps-dark.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/reference/gsk/images/caps-light.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
81
docs/reference/gsk/images/caps.svg
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="caps.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="2.1493149"
|
||||
inkscape:cx="438.74445"
|
||||
inkscape:cy="288.69664"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1123"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="swatch1"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
|
||||
d="m 73.096455,77.084329 c 0,0 6.439436,18.711677 18.172676,26.635721"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 94.214979,99.722163 c -4.705477,-3.177832 -9.013472,-9.233699 -11.888672,-14.531169 -2.8752,-5.29748 -4.353516,-9.587833 -4.353516,-9.587833 L 68.51576,78.85704 c 0,0 1.743663,5.064163 5.021484,11.103457 3.277822,6.039297 8.05428,13.302573 15.082031,18.048773"
|
||||
id="path1-1"
|
||||
sodipodi:nodetypes="csccsc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
|
||||
d="m 112.14213,75.736452 c 0,0 6.43944,18.711675 18.17268,26.635728"
|
||||
id="path1-4"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 132.83284,98.590758 c -4.70551,-3.177807 -9.01347,-9.233742 -11.88867,-14.53125 -2.8752,-5.297508 -4.35351,-9.587891 -4.35351,-9.587891 -0.89828,-2.611719 -3.74396,-4.000453 -6.35547,-3.101562 -2.61172,0.898277 -4.00046,3.743958 -3.10157,6.355468 0,0 1.74367,5.064185 5.02149,11.103516 3.27782,6.039331 8.05428,13.302641 15.08203,18.048851"
|
||||
id="path1-4-5"
|
||||
sodipodi:nodetypes="cscccsc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
|
||||
d="m 154.10911,73.84561 c 0,0 6.43943,18.711677 18.17267,26.63572"
|
||||
id="path1-0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 175.16683,96.47344 C 170.46135,93.295592 166.1514,87.239699 163.2762,81.94219 160.401,76.644682 158.92269,72.3543 158.92269,72.3543 l -1.62696,-4.728516 -9.45508,3.253906 1.62696,4.728516 c 0,0 1.74171,5.066138 5.01953,11.105469 3.27782,6.039331 8.05428,13.300675 15.08203,18.046895"
|
||||
id="path1-4-1"
|
||||
sodipodi:nodetypes="csccccsc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
BIN
docs/reference/gsk/images/join-dark.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/reference/gsk/images/join-light.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
72
docs/reference/gsk/images/join.svg
Normal file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="join.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.75989759"
|
||||
inkscape:cx="118.43701"
|
||||
inkscape:cy="570.47161"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1123"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="m 12.748727,122.15935 c 4.449842,-15.44496 4.175632,-15.92044 16.908788,-26.568585 0,0 8.478091,16.421195 35.150804,33.660325"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 17.59375,123.72266 c 3.810222,-13.2249 3.591775,-14.13123 11.107422,-20.53516 1.289688,1.82186 2.604788,3.71164 4.800781,6.23242 5.743296,6.59272 14.891452,15.32766 28.632813,24.20899 M 67.5625,125.23047 C 54.631174,116.87268 46.204933,108.77726 41.041016,102.84961 35.877098,96.921954 34.140625,93.474609 34.140625,93.474609 l -2.861328,-5.544922 -4.789063,4.003907 C 13.673794,102.6514 12.411529,105.58692 7.984375,120.95313"
|
||||
id="path1-3"
|
||||
sodipodi:nodetypes="ccsccscccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="m 147.99215,122.60257 c 4.44988,-15.44499 4.17566,-15.92048 16.90889,-26.568657 0,0 8.47814,16.421267 35.15101,33.660397"
|
||||
id="path1-5"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 152.79687,123.98633 c 3.81053,-13.22586 3.59005,-14.12993 11.10743,-20.53516 1.28968,1.82184 2.6029,3.71173 4.79882,6.23242 5.74333,6.59274 14.89333,15.32961 28.63477,24.21094 m 5.42773,-8.40039 c -12.9314,-8.35778 -21.35753,-16.45123 -26.52148,-22.37891 -5.16395,-5.927668 -6.90039,-9.374996 -6.90039,-9.374996 l -7.65039,-1.541015 C 148.87685,102.91706 147.61469,105.85252 143.1875,121.21875"
|
||||
id="path1-5-8"
|
||||
sodipodi:nodetypes="ccsccsccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="m 79.729342,122.0734 c 4.449844,-15.44499 4.175634,-15.92048 16.908794,-26.568654 0,0 8.478114,16.421264 35.150984,33.660394"
|
||||
id="path1-7"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 84.533203,123.19336 c 3.810222,-13.22495 3.591776,-14.1312 11.107422,-20.53516 1.289696,1.82187 2.604775,3.71164 4.800785,6.23243 5.74333,6.59275 14.89136,15.32765 28.63281,24.20898 m 5.42969,-8.39844 c -12.93141,-8.35778 -21.35949,-16.45122 -26.52344,-22.3789 -5.16395,-5.927686 -6.90039,-9.375004 -6.90039,-9.375004 -0.69636,-1.348401 -1.967393,-2.307601 -3.45508,-2.607422 -1.487509,-0.300132 -3.03083,0.09145 -4.195313,1.064453 C 80.61325,102.12215 79.350982,105.05756 74.923828,120.42383"
|
||||
id="path1-7-4"
|
||||
sodipodi:nodetypes="ccsccscccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/reference/gsk/images/path-dark.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/reference/gsk/images/path-light.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
108
docs/reference/gsk/images/path.svg
Normal file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="path.svg"
|
||||
inkscape:export-filename="path-dark.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.75989759"
|
||||
inkscape:cx="397.42198"
|
||||
inkscape:cy="561.25984"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1123"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 128.28233,149.47912 154.23127,90.244656 86.340809,68.820468"
|
||||
id="path3" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;fill-opacity:1;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 33.956804,113.31099 c -9.049189,-11.90893 5.40551,-40.570358 20.351208,-39.990271 26.687501,1.035822 4.06495,71.984581 30.695656,74.009711 19.642072,1.49368 41.962402,-34.42048 30.634382,-50.536241 C 99.663884,74.068616 50.763019,135.42833 33.956804,113.31099 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="sssss" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2"
|
||||
cx="34.517788"
|
||||
cy="113.66589"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8"
|
||||
cx="55.045853"
|
||||
cy="73.566689"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-5"
|
||||
cx="55.045834"
|
||||
cy="73.831245"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-8-9"
|
||||
cx="55.045834"
|
||||
cy="73.831245"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-8-9-7"
|
||||
cx="86.398613"
|
||||
cy="68.897667"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-8-9-7-8"
|
||||
cx="153.87553"
|
||||
cy="90.112595"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7"
|
||||
cx="84.803238"
|
||||
cy="147.51736"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-8-7-2"
|
||||
cx="115.28235"
|
||||
cy="96.705284"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-8-9-7-9"
|
||||
cx="127.92362"
|
||||
cy="150.03427"
|
||||
r="1.5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -222,7 +222,7 @@ It is possible to set accessible attributes in UI files as well:
|
||||
<accessibility>
|
||||
<property name="label">Download</property>
|
||||
<relation name="labelled-by">label1</relation>
|
||||
/accessibility>
|
||||
</accessibility>
|
||||
</object>
|
||||
```
|
||||
|
||||
|
||||
@@ -27,9 +27,7 @@
|
||||
#include "gdkmacosdisplay.h"
|
||||
#include "gdkmacossurface.h"
|
||||
|
||||
#import <OpenGL/OpenGL.h>
|
||||
#import <OpenGL/gl3.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <epoxy/gl.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "gdkconfig.h"
|
||||
|
||||
#include <OpenGL/gl3.h>
|
||||
#include <OpenGL/CGLIOSurface.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
@@ -499,7 +498,7 @@ gdk_macos_gl_context_begin_frame (GdkDrawContext *context,
|
||||
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
|
||||
gdk_macos_gl_context_allocate (self);
|
||||
|
||||
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, region);
|
||||
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, depth, region);
|
||||
|
||||
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
|
||||
CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, self->fbo));
|
||||
|
||||
@@ -2857,7 +2857,10 @@ gdk_event_translate (MSG *msg,
|
||||
break;
|
||||
|
||||
case WM_SYSCOMMAND:
|
||||
switch (msg->wParam)
|
||||
/* From: https://learn.microsoft.com/en-us/windows/win32/menurc/wm-syscommand?redirectedfrom=MSDN
|
||||
* To obtain the correct result when testing the value of wParam,
|
||||
* an application must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator. */
|
||||
switch (msg->wParam & 0xFFF0)
|
||||
{
|
||||
case SC_MINIMIZE:
|
||||
case SC_RESTORE:
|
||||
@@ -2964,6 +2967,14 @@ gdk_event_translate (MSG *msg,
|
||||
windowpos = (WINDOWPOS *) msg->lParam;
|
||||
windowpos->cx = our_mmi.ptMaxSize.x;
|
||||
windowpos->cy = our_mmi.ptMaxSize.y;
|
||||
|
||||
if (!_gdk_win32_surface_lacks_wm_decorations (window) &&
|
||||
!(windowpos->flags & SWP_NOCLIENTSIZE) &&
|
||||
window->width == impl->next_layout.configured_width &&
|
||||
window->height == impl->next_layout.configured_height)
|
||||
{
|
||||
impl->inhibit_configure = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
impl->maximizing = FALSE;
|
||||
|
||||
@@ -1230,18 +1230,11 @@ get_effective_window_decorations (GdkSurface *window,
|
||||
|
||||
*decoration |= GDK_DECOR_MINIMIZE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else if (impl->hint_flags & GDK_HINT_MAX_SIZE)
|
||||
{
|
||||
*decoration = GDK_DECOR_ALL | GDK_DECOR_MAXIMIZE;
|
||||
*decoration |= GDK_DECOR_MINIMIZE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
*decoration = (GDK_DECOR_ALL | GDK_DECOR_MINIMIZE | GDK_DECOR_MAXIMIZE);
|
||||
*decoration = GDK_DECOR_ALL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -271,6 +271,8 @@ collect_reused_child_nodes (GskRenderer *renderer,
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
|
||||
default:
|
||||
|
||||
@@ -859,6 +861,8 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_GL_SHADER_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
default:
|
||||
break; /* Fallback */
|
||||
}
|
||||
|
||||
@@ -255,6 +255,8 @@ node_supports_2d_transform (const GskRenderNode *node)
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
return TRUE;
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
@@ -309,6 +311,8 @@ node_supports_transform (const GskRenderNode *node)
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
return TRUE;
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
@@ -1241,9 +1245,9 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job,
|
||||
done:
|
||||
if (scale_x < 0 || scale_y < 0)
|
||||
{
|
||||
GskTransform *transform = gsk_transform_translate (NULL,
|
||||
&GRAPHENE_POINT_INIT (scale_x < 0 ? - surface_width : 0,
|
||||
scale_y < 0 ? - surface_height : 0));
|
||||
GskTransform *transform = gsk_transform_translate (gsk_transform_scale (NULL, scale_x < 0 ? -1 : 1, scale_y < 0 ? -1 : 1),
|
||||
&GRAPHENE_POINT_INIT (scale_x < 0 ? - (node->bounds.size.width + 2 * node->bounds.origin.x) : 0,
|
||||
scale_y < 0 ? - (node->bounds.size.height + 2 * node->bounds.origin.y) : 0));
|
||||
gsk_gl_render_job_push_modelview (job, transform);
|
||||
gsk_transform_unref (transform);
|
||||
}
|
||||
@@ -4089,6 +4093,14 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job,
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_NOT_A_RENDER_NODE:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
|
||||
@@ -20,9 +20,13 @@
|
||||
#define __GSK_H_INSIDE__
|
||||
|
||||
#include <gsk/gskenums.h>
|
||||
#include <gsk/gskpath.h>
|
||||
#include <gsk/gskpathbuilder.h>
|
||||
#include <gsk/gskpathpoint.h>
|
||||
#include <gsk/gskrenderer.h>
|
||||
#include <gsk/gskrendernode.h>
|
||||
#include <gsk/gskroundedrect.h>
|
||||
#include <gsk/gskstroke.h>
|
||||
#include <gsk/gsktransform.h>
|
||||
#include <gsk/gskglshader.h>
|
||||
|
||||
|
||||
115
gsk/gskboundingboxprivate.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GskBoundingBox GskBoundingBox;
|
||||
|
||||
struct _GskBoundingBox {
|
||||
graphene_point_t min;
|
||||
graphene_point_t max;
|
||||
};
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init (GskBoundingBox *self,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b)
|
||||
{
|
||||
self->min.x = MIN (a->x, b->x);
|
||||
self->min.y = MIN (a->y, b->y);
|
||||
self->max.x = MAX (a->x, b->x);
|
||||
self->max.y = MAX (a->y, b->y);
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init_copy (GskBoundingBox *self,
|
||||
const GskBoundingBox *src)
|
||||
{
|
||||
self->min = src->min;
|
||||
self->max = src->max;
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init_from_rect (GskBoundingBox *self,
|
||||
const graphene_rect_t *bounds)
|
||||
{
|
||||
self->min = bounds->origin;
|
||||
self->max.x = bounds->origin.x + bounds->size.width;
|
||||
self->max.y = bounds->origin.y + bounds->size.height;
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_bounding_box_expand (GskBoundingBox *self,
|
||||
const graphene_point_t *p)
|
||||
{
|
||||
self->min.x = MIN (self->min.x, p->x);
|
||||
self->min.y = MIN (self->min.y, p->y);
|
||||
self->max.x = MAX (self->max.x, p->x);
|
||||
self->max.y = MAX (self->max.y, p->y);
|
||||
}
|
||||
|
||||
static inline graphene_rect_t *
|
||||
gsk_bounding_box_to_rect (const GskBoundingBox *self,
|
||||
graphene_rect_t *rect)
|
||||
{
|
||||
rect->origin = self->min;
|
||||
rect->size.width = self->max.x - self->min.x;
|
||||
rect->size.height = self->max.y - self->min.y;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_contains_point (const GskBoundingBox *self,
|
||||
const graphene_point_t *p)
|
||||
{
|
||||
return self->min.x <= p->x && p->x <= self->max.x &&
|
||||
self->min.y <= p->y && p->y <= self->max.y;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_contains_point_with_epsilon (const GskBoundingBox *self,
|
||||
const graphene_point_t *p,
|
||||
float epsilon)
|
||||
{
|
||||
return self->min.x - epsilon <= p->x && p->x <= self->max.x + epsilon &&
|
||||
self->min.y - epsilon <= p->y && p->y <= self->max.y + epsilon;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_intersection (const GskBoundingBox *a,
|
||||
const GskBoundingBox *b,
|
||||
GskBoundingBox *res)
|
||||
{
|
||||
graphene_point_t min, max;
|
||||
|
||||
min.x = MAX (a->min.x, b->min.x);
|
||||
min.y = MAX (a->min.y, b->min.y);
|
||||
max.x = MIN (a->max.x, b->max.x);
|
||||
max.y = MIN (a->max.y, b->max.y);
|
||||
|
||||
if (res)
|
||||
gsk_bounding_box_init (res, &min, &max);
|
||||
|
||||
return min.x <= max.x && min.y <= max.y;
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_bounding_box_union (const GskBoundingBox *a,
|
||||
const GskBoundingBox *b,
|
||||
GskBoundingBox *res)
|
||||
{
|
||||
graphene_point_t min, max;
|
||||
|
||||
min.x = MIN (a->min.x, b->min.x);
|
||||
min.y = MIN (a->min.y, b->min.y);
|
||||
max.x = MAX (a->max.x, b->max.x);
|
||||
max.y = MAX (a->max.y, b->max.y);
|
||||
|
||||
gsk_bounding_box_init (res, &min, &max);
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
834
gsk/gskcontour.c
Normal file
@@ -0,0 +1,834 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
#include "gskcurveprivate.h"
|
||||
#include "gskpathbuilder.h"
|
||||
#include "gskpathprivate.h"
|
||||
#include "gskpathpointprivate.h"
|
||||
#include "gsksplineprivate.h"
|
||||
#include "gskstrokeprivate.h"
|
||||
|
||||
typedef struct _GskContourClass GskContourClass;
|
||||
|
||||
struct _GskContour
|
||||
{
|
||||
const GskContourClass *klass;
|
||||
};
|
||||
|
||||
struct _GskContourClass
|
||||
{
|
||||
gsize struct_size;
|
||||
const char *type_name;
|
||||
|
||||
void (* copy) (const GskContour *contour,
|
||||
GskContour *dest);
|
||||
gsize (* get_size) (const GskContour *contour);
|
||||
GskPathFlags (* get_flags) (const GskContour *contour);
|
||||
void (* print) (const GskContour *contour,
|
||||
GString *string);
|
||||
gboolean (* get_bounds) (const GskContour *contour,
|
||||
GskBoundingBox *bounds);
|
||||
gboolean (* get_stroke_bounds) (const GskContour *contour,
|
||||
const GskStroke *stroke,
|
||||
GskBoundingBox *bounds);
|
||||
void (* get_start_end) (const GskContour *self,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end);
|
||||
gboolean (* foreach) (const GskContour *contour,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
GskContour * (* reverse) (const GskContour *contour);
|
||||
int (* get_winding) (const GskContour *contour,
|
||||
const graphene_point_t *point);
|
||||
gsize (* get_n_points) (const GskContour *contour);
|
||||
gboolean (* get_closest_point) (const GskContour *contour,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
GskRealPathPoint *result,
|
||||
float *out_dist);
|
||||
void (* get_position) (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *position);
|
||||
void (* get_tangent) (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent);
|
||||
float (* get_curvature) (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *center);
|
||||
void (* add_segment) (const GskContour *contour,
|
||||
GskPathBuilder *builder,
|
||||
gboolean emit_move_to,
|
||||
GskRealPathPoint *start,
|
||||
GskRealPathPoint *end);
|
||||
};
|
||||
|
||||
/* {{{ Utilities */
|
||||
|
||||
#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f))
|
||||
#define RAD_TO_DEG(x) ((x) / (G_PI / 180.f))
|
||||
|
||||
static void
|
||||
_g_string_append_double (GString *string,
|
||||
double d)
|
||||
{
|
||||
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
||||
|
||||
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d);
|
||||
g_string_append (string, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
_g_string_append_point (GString *string,
|
||||
const graphene_point_t *pt)
|
||||
{
|
||||
_g_string_append_double (string, pt->x);
|
||||
g_string_append_c (string, ' ');
|
||||
_g_string_append_double (string, pt->y);
|
||||
}
|
||||
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Standard */
|
||||
|
||||
typedef struct _GskStandardContour GskStandardContour;
|
||||
struct _GskStandardContour
|
||||
{
|
||||
GskContour contour;
|
||||
|
||||
GskPathFlags flags;
|
||||
|
||||
gsize n_ops;
|
||||
gsize n_points;
|
||||
graphene_point_t *points;
|
||||
gskpathop ops[];
|
||||
};
|
||||
|
||||
static gsize
|
||||
gsk_standard_contour_compute_size (gsize n_ops,
|
||||
gsize n_points)
|
||||
{
|
||||
return sizeof (GskStandardContour)
|
||||
+ sizeof (gskpathop) * n_ops
|
||||
+ sizeof (graphene_point_t) * n_points;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_init (GskContour *contour,
|
||||
GskPathFlags flags,
|
||||
const graphene_point_t *points,
|
||||
gsize n_points,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
ptrdiff_t offset);
|
||||
|
||||
static void
|
||||
gsk_standard_contour_copy (const GskContour *contour,
|
||||
GskContour *dest)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
|
||||
gsk_standard_contour_init (dest, self->flags, self->points, self->n_points, self->ops, self->n_ops, 0);
|
||||
}
|
||||
|
||||
static gsize
|
||||
gsk_standard_contour_get_size (const GskContour *contour)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
|
||||
return gsk_standard_contour_compute_size (self->n_ops, self->n_points);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_standard_contour_foreach (const GskContour *contour,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
gsize i;
|
||||
|
||||
for (i = 0; i < self->n_ops; i ++)
|
||||
{
|
||||
if (!gsk_pathop_foreach (self->ops[i], func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_reverse (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder *builder = user_data;
|
||||
GskCurve c, r;
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
return TRUE;
|
||||
|
||||
if (op == GSK_PATH_CLOSE)
|
||||
op = GSK_PATH_LINE;
|
||||
|
||||
gsk_curve_init_foreach (&c, op, pts, n_pts);
|
||||
gsk_curve_reverse (&c, &r);
|
||||
gsk_curve_builder_to (&r, builder);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GskContour *
|
||||
gsk_standard_contour_reverse (const GskContour *contour)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
GskContour *res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_builder_move_to (builder, self->points[self->n_points - 1].x,
|
||||
self->points[self->n_points - 1].y);
|
||||
|
||||
for (int i = self->n_ops - 1; i >= 0; i--)
|
||||
gsk_pathop_foreach (self->ops[i], add_reverse, builder);
|
||||
|
||||
if (self->flags & GSK_PATH_CLOSED)
|
||||
gsk_path_builder_close (builder);
|
||||
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
g_assert (gsk_path_get_n_contours (path) == 1);
|
||||
|
||||
res = gsk_contour_dup (gsk_path_get_contour (path, 0));
|
||||
|
||||
gsk_path_unref (path);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static GskPathFlags
|
||||
gsk_standard_contour_get_flags (const GskContour *contour)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
|
||||
return self->flags;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_print (const GskContour *contour,
|
||||
GString *string)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
gsize i;
|
||||
|
||||
for (i = 0; i < self->n_ops; i ++)
|
||||
{
|
||||
const graphene_point_t *pt = gsk_pathop_points (self->ops[i]);
|
||||
|
||||
switch (gsk_pathop_op (self->ops[i]))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
g_string_append (string, "M ");
|
||||
_g_string_append_point (string, &pt[0]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
g_string_append (string, " Z");
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
g_string_append (string, " L ");
|
||||
_g_string_append_point (string, &pt[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
g_string_append (string, " Q ");
|
||||
_g_string_append_point (string, &pt[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &pt[2]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
g_string_append (string, " C ");
|
||||
_g_string_append_point (string, &pt[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &pt[2]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &pt[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_standard_contour_get_bounds (const GskContour *contour,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
gsize i;
|
||||
|
||||
if (self->n_points == 0)
|
||||
return FALSE;
|
||||
|
||||
gsk_bounding_box_init (bounds, &self->points[0], &self->points[0]);
|
||||
for (i = 1; i < self->n_points; i ++)
|
||||
gsk_bounding_box_expand (bounds, &self->points[i]);
|
||||
|
||||
return bounds->max.x > bounds->min.x && bounds->max.y > bounds->min.y;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_stroke_bounds (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
struct {
|
||||
GskBoundingBox *bounds;
|
||||
float lw;
|
||||
float mw;
|
||||
} *data = user_data;
|
||||
GskBoundingBox bounds;
|
||||
|
||||
for (int i = 1; i < n_pts - 1; i++)
|
||||
{
|
||||
gsk_bounding_box_init (&bounds,
|
||||
&GRAPHENE_POINT_INIT (pts[i].x - data->lw/2, pts[i].y - data->lw/2),
|
||||
&GRAPHENE_POINT_INIT (pts[i].x + data->lw/2, pts[i].y + data->lw/2));
|
||||
gsk_bounding_box_union (&bounds, data->bounds, data->bounds);
|
||||
}
|
||||
|
||||
gsk_bounding_box_init (&bounds,
|
||||
&GRAPHENE_POINT_INIT (pts[n_pts - 1].x - data->mw/2, pts[n_pts - 1].y - data->mw/2),
|
||||
&GRAPHENE_POINT_INIT (pts[n_pts - 1].x + data->mw/2, pts[n_pts - 1].y + data->mw/2));
|
||||
gsk_bounding_box_union (&bounds, data->bounds, data->bounds);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_standard_contour_get_stroke_bounds (const GskContour *contour,
|
||||
const GskStroke *stroke,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
struct {
|
||||
GskBoundingBox *bounds;
|
||||
float lw;
|
||||
float mw;
|
||||
} data;
|
||||
|
||||
data.bounds = bounds;
|
||||
data.lw = stroke->line_width;
|
||||
data.mw = gsk_stroke_get_join_width (stroke);
|
||||
|
||||
gsk_bounding_box_init (bounds,
|
||||
&GRAPHENE_POINT_INIT (self->points[0].x - data.mw/2, self->points[0].y - data.mw/2),
|
||||
&GRAPHENE_POINT_INIT (self->points[0].x + data.mw/2, self->points[0].y + data.mw/2));
|
||||
|
||||
gsk_standard_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, add_stroke_bounds, &data);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_get_start_end (const GskContour *contour,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end)
|
||||
{
|
||||
const GskStandardContour *self = (const GskStandardContour *) contour;
|
||||
|
||||
if (start)
|
||||
*start = self->points[0];
|
||||
|
||||
if (end)
|
||||
*end = self->points[self->n_points - 1];
|
||||
}
|
||||
|
||||
static int
|
||||
gsk_standard_contour_get_winding (const GskContour *contour,
|
||||
const graphene_point_t *point)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
int winding = 0;
|
||||
|
||||
for (gsize i = 0; i < self->n_ops; i ++)
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
if (gsk_pathop_op (self->ops[i]) == GSK_PATH_MOVE)
|
||||
continue;
|
||||
|
||||
gsk_curve_init (&c, self->ops[i]);
|
||||
winding += gsk_curve_get_crossing (&c, point);
|
||||
}
|
||||
|
||||
if ((self->flags & GSK_PATH_CLOSED) == 0)
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CLOSE,
|
||||
(const graphene_point_t[]) { self->points[self->n_points - 1],
|
||||
self->points[0] }));
|
||||
|
||||
winding += gsk_curve_get_crossing (&c, point);
|
||||
}
|
||||
|
||||
return winding;
|
||||
}
|
||||
|
||||
static gsize
|
||||
gsk_standard_contour_get_n_points (const GskContour *contour)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
|
||||
return self->n_ops;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_standard_contour_get_closest_point (const GskContour *contour,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
GskRealPathPoint *result,
|
||||
float *out_dist)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
unsigned int best_idx = G_MAXUINT;
|
||||
float best_t = 0;
|
||||
|
||||
g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE);
|
||||
|
||||
if (self->n_ops == 1)
|
||||
{
|
||||
float dist;
|
||||
|
||||
dist = graphene_point_distance (point, &self->points[0], NULL, NULL);
|
||||
if (dist <= threshold)
|
||||
{
|
||||
*out_dist = dist;
|
||||
result->idx = 0;
|
||||
result->t = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (gsize i = 0; i < self->n_ops; i ++)
|
||||
{
|
||||
GskCurve c;
|
||||
float distance, t;
|
||||
|
||||
if (gsk_pathop_op (self->ops[i]) == GSK_PATH_MOVE)
|
||||
continue;
|
||||
|
||||
gsk_curve_init (&c, self->ops[i]);
|
||||
if (gsk_curve_get_closest_point (&c, point, threshold, &distance, &t) &&
|
||||
distance < threshold)
|
||||
{
|
||||
best_idx = i;
|
||||
best_t = t;
|
||||
threshold = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_idx != G_MAXUINT)
|
||||
{
|
||||
*out_dist = threshold;
|
||||
result->idx = best_idx;
|
||||
result->t = best_t;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_get_position (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *position)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
GskCurve curve;
|
||||
|
||||
if (G_UNLIKELY (point->idx == 0))
|
||||
{
|
||||
*position = self->points[0];
|
||||
return;
|
||||
}
|
||||
|
||||
gsk_curve_init (&curve, self->ops[point->idx]);
|
||||
gsk_curve_get_point (&curve, point->t, position);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_get_tangent (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
GskCurve curve;
|
||||
gsize idx;
|
||||
float t;
|
||||
|
||||
if (G_UNLIKELY (point->idx == 0))
|
||||
{
|
||||
graphene_vec2_init (tangent, 1, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
idx = point->idx;
|
||||
t = point->t;
|
||||
|
||||
if (t == 0 && direction == GSK_PATH_START)
|
||||
{
|
||||
/* Look at the previous segment */
|
||||
if (idx > 1)
|
||||
{
|
||||
idx--;
|
||||
t = 1;
|
||||
}
|
||||
else if (self->flags & GSK_PATH_CLOSED)
|
||||
{
|
||||
idx = self->n_ops - 1;
|
||||
t = 1;
|
||||
}
|
||||
}
|
||||
else if (t == 1 && direction == GSK_PATH_END)
|
||||
{
|
||||
/* Look at the next segment */
|
||||
if (idx < self->n_ops - 1)
|
||||
{
|
||||
idx++;
|
||||
t = 0;
|
||||
}
|
||||
else if (self->flags & GSK_PATH_CLOSED)
|
||||
{
|
||||
idx = 1;
|
||||
t = 0;
|
||||
}
|
||||
}
|
||||
|
||||
gsk_curve_init (&curve, self->ops[idx]);
|
||||
gsk_curve_get_tangent (&curve, t, tangent);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_standard_contour_get_curvature (const GskContour *contour,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *center)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
GskCurve curve;
|
||||
|
||||
if (G_UNLIKELY (point->idx == 0))
|
||||
return 0;
|
||||
|
||||
gsk_curve_init (&curve, self->ops[point->idx]);
|
||||
return gsk_curve_get_curvature (&curve, point->t, center);
|
||||
}
|
||||
|
||||
static void
|
||||
add_curve (GskCurve *curve,
|
||||
GskPathBuilder *builder,
|
||||
gboolean *emit_move_to)
|
||||
{
|
||||
if (*emit_move_to)
|
||||
{
|
||||
const graphene_point_t *s;
|
||||
|
||||
s = gsk_curve_get_start_point (curve);
|
||||
gsk_path_builder_move_to (builder, s->x, s->y);
|
||||
*emit_move_to = FALSE;
|
||||
}
|
||||
gsk_curve_builder_to (curve, builder);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_add_segment (const GskContour *contour,
|
||||
GskPathBuilder *builder,
|
||||
gboolean emit_move_to,
|
||||
GskRealPathPoint *start,
|
||||
GskRealPathPoint *end)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
GskCurve c, c1, c2;
|
||||
|
||||
gsk_curve_init (&c, self->ops[start->idx]);
|
||||
|
||||
if (start->idx == end->idx)
|
||||
{
|
||||
gsk_curve_segment (&c, start->t, end->t, &c1);
|
||||
add_curve (&c1, builder, &emit_move_to);
|
||||
return;
|
||||
}
|
||||
if (start->t == 0)
|
||||
{
|
||||
add_curve (&c, builder, &emit_move_to);
|
||||
}
|
||||
else if (start->t < 1)
|
||||
{
|
||||
gsk_curve_split (&c, start->t, &c1, &c2);
|
||||
add_curve (&c2, builder, &emit_move_to);
|
||||
}
|
||||
|
||||
for (gsize i = start->idx + 1; i < end->idx; i++)
|
||||
{
|
||||
gsk_curve_init (&c, self->ops[i]);
|
||||
add_curve (&c, builder, &emit_move_to);
|
||||
}
|
||||
|
||||
gsk_curve_init (&c, self->ops[end->idx]);
|
||||
if (c.op == GSK_PATH_CLOSE)
|
||||
c.op = GSK_PATH_LINE;
|
||||
|
||||
if (end->t == 1)
|
||||
{
|
||||
add_curve (&c, builder, &emit_move_to);
|
||||
}
|
||||
else if (end->t > 0)
|
||||
{
|
||||
gsk_curve_split (&c, end->t, &c1, &c2);
|
||||
add_curve (&c, builder, &emit_move_to);
|
||||
}
|
||||
}
|
||||
|
||||
static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
|
||||
{
|
||||
sizeof (GskStandardContour),
|
||||
"GskStandardContour",
|
||||
gsk_standard_contour_copy,
|
||||
gsk_standard_contour_get_size,
|
||||
gsk_standard_contour_get_flags,
|
||||
gsk_standard_contour_print,
|
||||
gsk_standard_contour_get_bounds,
|
||||
gsk_standard_contour_get_stroke_bounds,
|
||||
gsk_standard_contour_get_start_end,
|
||||
gsk_standard_contour_foreach,
|
||||
gsk_standard_contour_reverse,
|
||||
gsk_standard_contour_get_winding,
|
||||
gsk_standard_contour_get_n_points,
|
||||
gsk_standard_contour_get_closest_point,
|
||||
gsk_standard_contour_get_position,
|
||||
gsk_standard_contour_get_tangent,
|
||||
gsk_standard_contour_get_curvature,
|
||||
gsk_standard_contour_add_segment,
|
||||
};
|
||||
|
||||
/* You must ensure the contour has enough size allocated,
|
||||
* see gsk_standard_contour_compute_size()
|
||||
*/
|
||||
static void
|
||||
gsk_standard_contour_init (GskContour *contour,
|
||||
GskPathFlags flags,
|
||||
const graphene_point_t *points,
|
||||
gsize n_points,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
gssize offset)
|
||||
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
gsize i;
|
||||
|
||||
self->contour.klass = &GSK_STANDARD_CONTOUR_CLASS;
|
||||
|
||||
self->flags = flags;
|
||||
self->n_ops = n_ops;
|
||||
self->n_points = n_points;
|
||||
self->points = (graphene_point_t *) &self->ops[n_ops];
|
||||
memcpy (self->points, points, sizeof (graphene_point_t) * n_points);
|
||||
|
||||
offset += self->points - points;
|
||||
for (i = 0; i < n_ops; i++)
|
||||
{
|
||||
self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]),
|
||||
gsk_pathop_points (ops[i]) + offset);
|
||||
}
|
||||
}
|
||||
|
||||
GskContour *
|
||||
gsk_standard_contour_new (GskPathFlags flags,
|
||||
const graphene_point_t *points,
|
||||
gsize n_points,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
gssize offset)
|
||||
{
|
||||
GskContour *contour;
|
||||
|
||||
contour = g_malloc0 (gsk_standard_contour_compute_size (n_ops, n_points));
|
||||
|
||||
gsk_standard_contour_init (contour, flags, points, n_points, ops, n_ops, offset);
|
||||
|
||||
return contour;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ API */
|
||||
|
||||
gsize
|
||||
gsk_contour_get_size (const GskContour *self)
|
||||
{
|
||||
return self->klass->get_size (self);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_copy (GskContour *dest,
|
||||
const GskContour *src)
|
||||
{
|
||||
src->klass->copy (src, dest);
|
||||
}
|
||||
|
||||
GskContour *
|
||||
gsk_contour_dup (const GskContour *src)
|
||||
{
|
||||
GskContour *copy;
|
||||
|
||||
copy = g_malloc0 (gsk_contour_get_size (src));
|
||||
gsk_contour_copy (copy, src);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
GskContour *
|
||||
gsk_contour_reverse (const GskContour *src)
|
||||
{
|
||||
return src->klass->reverse (src);
|
||||
}
|
||||
|
||||
GskPathFlags
|
||||
gsk_contour_get_flags (const GskContour *self)
|
||||
{
|
||||
return self->klass->get_flags (self);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_print (const GskContour *self,
|
||||
GString *string)
|
||||
{
|
||||
self->klass->print (self, string);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_get_bounds (const GskContour *self,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
return self->klass->get_bounds (self, bounds);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_get_stroke_bounds (const GskContour *self,
|
||||
const GskStroke *stroke,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
return self->klass->get_stroke_bounds (self, stroke, bounds);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_foreach (const GskContour *self,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
return self->klass->foreach (self, tolerance, func, user_data);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_get_start_end (const GskContour *self,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end)
|
||||
{
|
||||
self->klass->get_start_end (self, start, end);
|
||||
}
|
||||
|
||||
int
|
||||
gsk_contour_get_winding (const GskContour *self,
|
||||
const graphene_point_t *point)
|
||||
{
|
||||
return self->klass->get_winding (self, point);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_get_closest_point (const GskContour *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
GskRealPathPoint *result,
|
||||
float *out_dist)
|
||||
{
|
||||
return self->klass->get_closest_point (self, point, threshold, result, out_dist);
|
||||
}
|
||||
|
||||
gsize
|
||||
gsk_contour_get_n_points (const GskContour *self)
|
||||
{
|
||||
return self->klass->get_n_points (self);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_get_position (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *pos)
|
||||
{
|
||||
self->klass->get_position (self, point, pos);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_get_tangent (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
self->klass->get_tangent (self, point, direction, tangent);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_contour_get_curvature (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *center)
|
||||
{
|
||||
return self->klass->get_curvature (self, point, center);
|
||||
}
|
||||
|
||||
void
|
||||
gsk_contour_add_segment (const GskContour *self,
|
||||
GskPathBuilder *builder,
|
||||
gboolean emit_move_to,
|
||||
GskRealPathPoint *start,
|
||||
GskRealPathPoint *end)
|
||||
{
|
||||
self->klass->add_segment (self, builder, emit_move_to, start, end);
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* vim:set foldmethod=marker expandtab: */
|
||||
83
gsk/gskcontourprivate.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpathprivate.h"
|
||||
#include "gskpathpointprivate.h"
|
||||
#include "gskpathopprivate.h"
|
||||
#include "gskboundingboxprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GskContour * gsk_standard_contour_new (GskPathFlags flags,
|
||||
const graphene_point_t *points,
|
||||
gsize n_points,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
gssize offset);
|
||||
|
||||
void gsk_contour_copy (GskContour * dest,
|
||||
const GskContour *src);
|
||||
GskContour * gsk_contour_dup (const GskContour *src);
|
||||
GskContour * gsk_contour_reverse (const GskContour *src);
|
||||
|
||||
gsize gsk_contour_get_size (const GskContour *self);
|
||||
GskPathFlags gsk_contour_get_flags (const GskContour *self);
|
||||
void gsk_contour_print (const GskContour *self,
|
||||
GString *string);
|
||||
gboolean gsk_contour_get_bounds (const GskContour *self,
|
||||
GskBoundingBox *bounds);
|
||||
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
|
||||
const GskStroke *stroke,
|
||||
GskBoundingBox *bounds);
|
||||
gboolean gsk_contour_foreach (const GskContour *self,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
void gsk_contour_get_start_end (const GskContour *self,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end);
|
||||
int gsk_contour_get_winding (const GskContour *self,
|
||||
const graphene_point_t *point);
|
||||
gsize gsk_contour_get_n_points (const GskContour *self);
|
||||
gboolean gsk_contour_get_closest_point (const GskContour *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
GskRealPathPoint *result,
|
||||
float *out_dist);
|
||||
void gsk_contour_get_position (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *pos);
|
||||
void gsk_contour_get_tangent (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent);
|
||||
float gsk_contour_get_curvature (const GskContour *self,
|
||||
GskRealPathPoint *point,
|
||||
graphene_point_t *center);
|
||||
void gsk_contour_add_segment (const GskContour *self,
|
||||
GskPathBuilder *builder,
|
||||
gboolean emit_move_to,
|
||||
GskRealPathPoint *start,
|
||||
GskRealPathPoint *end);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
1533
gsk/gskcurve.c
Normal file
157
gsk/gskcurveprivate.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpathopprivate.h"
|
||||
#include "gskpath.h"
|
||||
#include "gskboundingboxprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
typedef union _GskCurve GskCurve;
|
||||
|
||||
typedef struct _GskLineCurve GskLineCurve;
|
||||
typedef struct _GskQuadCurve GskQuadCurve;
|
||||
typedef struct _GskCubicCurve GskCubicCurve;
|
||||
|
||||
struct _GskLineCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean padding;
|
||||
|
||||
graphene_point_t points[2];
|
||||
};
|
||||
|
||||
struct _GskQuadCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[3];
|
||||
|
||||
graphene_point_t coeffs[3];
|
||||
};
|
||||
|
||||
struct _GskCubicCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[4];
|
||||
|
||||
graphene_point_t coeffs[4];
|
||||
};
|
||||
|
||||
union _GskCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
GskLineCurve line;
|
||||
GskQuadCurve quad;
|
||||
GskCubicCurve cubic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GSK_CURVE_LINE_REASON_STRAIGHT,
|
||||
GSK_CURVE_LINE_REASON_SHORT
|
||||
} GskCurveLineReason;
|
||||
|
||||
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data);
|
||||
|
||||
typedef gboolean (* GskCurveAddCurveFunc) (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data);
|
||||
|
||||
void gsk_curve_init (GskCurve *curve,
|
||||
gskpathop op);
|
||||
void gsk_curve_init_foreach (GskCurve *curve,
|
||||
GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts);
|
||||
|
||||
void gsk_curve_print (const GskCurve *curve,
|
||||
GString *string);
|
||||
|
||||
char * gsk_curve_to_string (const GskCurve *curve);
|
||||
|
||||
gskpathop gsk_curve_pathop (const GskCurve *curve);
|
||||
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
|
||||
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
|
||||
void gsk_curve_get_start_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_get_end_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_get_point (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_point_t *pos);
|
||||
void gsk_curve_get_tangent (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_reverse (const GskCurve *curve,
|
||||
GskCurve *reverse);
|
||||
void gsk_curve_split (const GskCurve *curve,
|
||||
float progress,
|
||||
GskCurve *start,
|
||||
GskCurve *end);
|
||||
void gsk_curve_segment (const GskCurve *curve,
|
||||
float start,
|
||||
float end,
|
||||
GskCurve *segment);
|
||||
gboolean gsk_curve_decompose (const GskCurve *curve,
|
||||
float tolerance,
|
||||
GskCurveAddLineFunc add_line_func,
|
||||
gpointer user_data);
|
||||
gboolean gsk_curve_decompose_curve (const GskCurve *curve,
|
||||
GskPathForeachFlags flags,
|
||||
float tolerance,
|
||||
GskCurveAddCurveFunc add_curve_func,
|
||||
gpointer user_data);
|
||||
|
||||
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
|
||||
|
||||
float gsk_curve_get_curvature (const GskCurve *curve,
|
||||
float t,
|
||||
graphene_point_t *center);
|
||||
void gsk_curve_get_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds);
|
||||
void gsk_curve_get_tight_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds);
|
||||
|
||||
int gsk_curve_get_crossing (const GskCurve *curve,
|
||||
const graphene_point_t *point);
|
||||
gboolean gsk_curve_get_closest_point (const GskCurve *curve,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_dist,
|
||||
float *out_t);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
142
gsk/gskenums.h
@@ -1,5 +1,5 @@
|
||||
/* GSK - The GTK Scene Kit
|
||||
* Copyright 2016 Endless
|
||||
* Copyright 2016 Endless
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@@ -51,7 +51,9 @@
|
||||
* @GSK_GL_SHADER_NODE: A node that uses OpenGL fragment shaders to render
|
||||
* @GSK_TEXTURE_SCALE_NODE: A node drawing a `GdkTexture` scaled and filtered (Since: 4.10)
|
||||
* @GSK_MASK_NODE: A node that masks one child with another (Since: 4.10)
|
||||
|
||||
* @GSK_FILL_NODE: A node that fills a path
|
||||
* @GSK_STROKE_NODE: A node that strokes a path
|
||||
|
||||
* The type of a node determines what the node is rendering.
|
||||
*/
|
||||
typedef enum {
|
||||
@@ -82,7 +84,9 @@ typedef enum {
|
||||
GSK_DEBUG_NODE,
|
||||
GSK_GL_SHADER_NODE,
|
||||
GSK_TEXTURE_SCALE_NODE,
|
||||
GSK_MASK_NODE
|
||||
GSK_MASK_NODE,
|
||||
GSK_FILL_NODE,
|
||||
GSK_STROKE_NODE
|
||||
} GskRenderNodeType;
|
||||
|
||||
/**
|
||||
@@ -170,6 +174,137 @@ typedef enum {
|
||||
GSK_CORNER_BOTTOM_LEFT
|
||||
} GskCorner;
|
||||
|
||||
/**
|
||||
* GskFillRule:
|
||||
* @GSK_FILL_RULE_WINDING: If the path crosses the ray from
|
||||
* left-to-right, counts +1. If the path crosses the ray
|
||||
* from right to left, counts -1. (Left and right are determined
|
||||
* from the perspective of looking along the ray from the starting
|
||||
* point.) If the total count is non-zero, the point will be filled.
|
||||
* @GSK_FILL_RULE_EVEN_ODD: Counts the total number of
|
||||
* intersections, without regard to the orientation of the contour. If
|
||||
* the total number of intersections is odd, the point will be
|
||||
* filled.
|
||||
*
|
||||
* `GskFillRule` is used to select how paths are filled.
|
||||
*
|
||||
* Whether or not a point is included in the fill is determined by taking
|
||||
* a ray from that point to infinity and looking at intersections with the
|
||||
* path. The ray can be in any direction, as long as it doesn't pass through
|
||||
* the end point of a segment or have a tricky intersection such as
|
||||
* intersecting tangent to the path.
|
||||
*
|
||||
* (Note that filling is not actually implemented in this way. This
|
||||
* is just a description of the rule that is applied.)
|
||||
*
|
||||
* New entries may be added in future versions.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_FILL_RULE_WINDING,
|
||||
GSK_FILL_RULE_EVEN_ODD
|
||||
} GskFillRule;
|
||||
|
||||
/**
|
||||
* GskLineCap:
|
||||
* @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start
|
||||
* and end point
|
||||
* @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle
|
||||
* is the start or end point
|
||||
* @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square
|
||||
* is the start or end point
|
||||
*
|
||||
* Specifies how to render the start and end points of contours or
|
||||
* dashes when stroking.
|
||||
*
|
||||
* The default line cap style is `GSK_LINE_CAP_BUTT`.
|
||||
*
|
||||
* <picture>
|
||||
* <source srcset="caps-dark.png" media="(prefers-color-scheme: dark)">
|
||||
* <img alt="Line Cap Styles" src="caps-light.png">
|
||||
* </picture>
|
||||
*
|
||||
* New entries may be added in future versions.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_LINE_CAP_BUTT,
|
||||
GSK_LINE_CAP_ROUND,
|
||||
GSK_LINE_CAP_SQUARE
|
||||
} GskLineCap;
|
||||
|
||||
/**
|
||||
* GskLineJoin:
|
||||
* @GSK_LINE_JOIN_MITER: Use a sharp angled corner
|
||||
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
|
||||
* the join point
|
||||
* @GSK_LINE_JOIN_BEVEL: use a cut-off join, the join is cut off at half
|
||||
* the line width from the joint point
|
||||
*
|
||||
* Specifies how to render the junction of two lines when stroking.
|
||||
*
|
||||
* The default line join style is `GSK_LINE_JOIN_MITER`.
|
||||
*
|
||||
* <picture>
|
||||
* <source srcset="join-dark.png" media="(prefers-color-scheme: dark)">
|
||||
* <img alt="Line Join Styles" src="join-light.png">
|
||||
* </picture>
|
||||
*
|
||||
* New entries may be added in future versions.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_LINE_JOIN_MITER,
|
||||
GSK_LINE_JOIN_ROUND,
|
||||
GSK_LINE_JOIN_BEVEL,
|
||||
} GskLineJoin;
|
||||
|
||||
/**
|
||||
* GskPathOperation:
|
||||
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the target point.
|
||||
* @GSK_PATH_CLOSE: A close operation ending the current contour with a line back
|
||||
* to the starting point. Two points describe the start and end of the line.
|
||||
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the start and
|
||||
* end point of a straight line.
|
||||
* @GSK_PATH_QUAD: A curve-to operation describing a quadratic Bézier curve
|
||||
* with 3 points describing the start point, the control point and the end
|
||||
* point of the curve.
|
||||
* @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve with 4
|
||||
* points describing the start point, the two control points and the end point
|
||||
* of the curve.
|
||||
*
|
||||
* Path operations are used to described segments of a `GskPath`.
|
||||
*
|
||||
* More values may be added in the future.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_PATH_MOVE,
|
||||
GSK_PATH_CLOSE,
|
||||
GSK_PATH_LINE,
|
||||
GSK_PATH_QUAD,
|
||||
GSK_PATH_CUBIC,
|
||||
} GskPathOperation;
|
||||
|
||||
/**
|
||||
* GskPathDirection:
|
||||
* @GSK_PATH_START: The side that leads to the start of the path
|
||||
* @GSK_PATH_END: The side that leads to the end of the path
|
||||
*
|
||||
* The values of the `GskPathDirection` enum are used to pick one
|
||||
* of the two sides of the path that at a given point on the path.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_PATH_START,
|
||||
GSK_PATH_END
|
||||
} GskPathDirection;
|
||||
|
||||
/**
|
||||
* GskSerializationError:
|
||||
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified
|
||||
@@ -274,4 +409,3 @@ typedef enum
|
||||
GSK_MASK_MODE_LUMINANCE,
|
||||
GSK_MASK_MODE_INVERTED_LUMINANCE
|
||||
} GskMaskMode;
|
||||
|
||||
|
||||
1415
gsk/gskpath.c
Normal file
136
gsk/gskpath.h
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* GskPathForeachFlags:
|
||||
* @GSK_PATH_FOREACH_ALLOW_ONLY_LINES: The default behavior, only allow lines.
|
||||
* @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of `GSK_PATH_QUAD` operations
|
||||
* @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations.
|
||||
* @GSK_PATH_FOREACH_ALLOW_ANY: Allow emission of any kind of operation.
|
||||
*
|
||||
* Flags that can be passed to gsk_path_foreach() to enable additional
|
||||
* features.
|
||||
*
|
||||
* By default, [method@Gsk.Path.foreach] will only emit a path with all operations
|
||||
* flattened to straight lines to allow for maximum compatibility. The only
|
||||
* operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FOREACH_ALLOW_ONLY_LINES = 0,
|
||||
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
|
||||
} GskPathForeachFlags;
|
||||
|
||||
/**
|
||||
* GskPathForeachFunc:
|
||||
* @op: The operation to perform
|
||||
* @pts: The points of the operation
|
||||
* @n_pts: The number of points
|
||||
* @user_data: The user data provided with the function
|
||||
*
|
||||
* Prototype of the callback to iterate throught the operations of
|
||||
* a path.
|
||||
*
|
||||
* Returns: %TRUE to continue evaluating the path, %FALSE to
|
||||
* immediately abort and not call the function again.
|
||||
*/
|
||||
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data);
|
||||
|
||||
#define GSK_TYPE_PATH (gsk_path_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_path_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_path_ref (GskPath *self);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_unref (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_print (GskPath *self,
|
||||
GString *string);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
char * gsk_path_to_string (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_path_parse (const char *string);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_to_cairo (GskPath *self,
|
||||
cairo_t *cr);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_is_empty (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_is_closed (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_get_bounds (GskPath *self,
|
||||
graphene_rect_t *bounds);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_get_stroke_bounds (GskPath *self,
|
||||
const GskStroke *stroke,
|
||||
graphene_rect_t *bounds);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_in_fill (GskPath *self,
|
||||
const graphene_point_t *point,
|
||||
GskFillRule fill_rule);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_get_start_point (GskPath *self,
|
||||
GskPathPoint *result);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_get_end_point (GskPath *self,
|
||||
GskPathPoint *result);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_get_closest_point (GskPath *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
GskPathPoint *result);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_foreach (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
|
||||
|
||||
G_END_DECLS
|
||||
1156
gsk/gskpathbuilder.c
Normal file
128
gsk/gskpathbuilder.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gskroundedrect.h>
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPathBuilder * gsk_path_builder_new (void);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *self);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_unref (GskPathBuilder *self);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_path_builder_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_path (GskPathBuilder *self,
|
||||
GskPath *path);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_reverse_path (GskPathBuilder *self,
|
||||
GskPath *path);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_cairo_path (GskPathBuilder *self,
|
||||
const cairo_path_t *path);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_layout (GskPathBuilder *self,
|
||||
PangoLayout *layout);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_rect (GskPathBuilder *self,
|
||||
const graphene_rect_t *rect);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
|
||||
const GskRoundedRect *rect);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_circle (GskPathBuilder *self,
|
||||
const graphene_point_t *center,
|
||||
float radius);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||
GskPath *path,
|
||||
const GskPathPoint *start,
|
||||
const GskPathPoint *end);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_rel_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_rel_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_rel_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_close (GskPathBuilder *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
|
||||
|
||||
G_END_DECLS
|
||||
172
gsk/gskpathopprivate.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpath.h"
|
||||
#include "gskpathbuilder.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
static inline
|
||||
gskpathop gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts);
|
||||
static inline
|
||||
const graphene_point_t *gsk_pathop_points (gskpathop pop);
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop);
|
||||
|
||||
static inline
|
||||
gboolean gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
/* included inline so tests can use them */
|
||||
static inline
|
||||
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
static inline
|
||||
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
|
||||
/* IMPLEMENTATION */
|
||||
|
||||
#define GSK_PATHOP_OPERATION_MASK (0x7)
|
||||
|
||||
static inline gskpathop
|
||||
gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts)
|
||||
{
|
||||
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
|
||||
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
|
||||
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
|
||||
}
|
||||
|
||||
static inline const graphene_point_t *
|
||||
gsk_pathop_points (gskpathop pop)
|
||||
{
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
|
||||
}
|
||||
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop)
|
||||
{
|
||||
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
switch (gsk_pathop_op (pop))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, user_data);
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, user_data);
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (op))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (op))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_line_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
236
gsk/gskpathpoint.c
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright © 2023 Red Hat, 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.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gskpathpointprivate.h"
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
#include "gdk/gdkprivate.h"
|
||||
|
||||
/**
|
||||
* GskPathPoint:
|
||||
*
|
||||
* `GskPathPoint` is an opaque type representing a point on a path.
|
||||
*
|
||||
* It can be queried for properties of the path at that point, such as its
|
||||
* tangent or its curvature.
|
||||
*
|
||||
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point].
|
||||
*
|
||||
* Note that `GskPathPoint` structs are meant to be stack-allocated, and
|
||||
* don't a reference to the path object they are obtained from. It is the
|
||||
* callers responsibility to keep a reference to the path as long as the
|
||||
* `GskPathPoint` is used.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
|
||||
G_DEFINE_BOXED_TYPE (GskPathPoint, gsk_path_point,
|
||||
gsk_path_point_copy,
|
||||
gsk_path_point_free)
|
||||
|
||||
|
||||
GskPathPoint *
|
||||
gsk_path_point_copy (GskPathPoint *point)
|
||||
{
|
||||
GskPathPoint *copy;
|
||||
|
||||
copy = g_new0 (GskPathPoint, 1);
|
||||
|
||||
memcpy (copy, point, sizeof (GskRealPathPoint));
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void
|
||||
gsk_path_point_free (GskPathPoint *point)
|
||||
{
|
||||
g_free (point);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_equal:
|
||||
* @point1: a `GskPathPoint`
|
||||
* @point2: another `GskPathPoint`
|
||||
*
|
||||
* Returns whether the two path points refer to the same
|
||||
* location on all paths.
|
||||
*
|
||||
* Note that the start- and endpoint of a closed contour
|
||||
* will compare nonequal according to this definition.
|
||||
* Use [method@Gsk.Path.is_closed] to find out if the
|
||||
* start- and endpoint of a concrete path refer to the
|
||||
* same location.
|
||||
*
|
||||
* Return: `TRUE` if @point1 and @point2 are equal
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_point_equal (const GskPathPoint *point1,
|
||||
const GskPathPoint *point2)
|
||||
{
|
||||
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
|
||||
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
|
||||
|
||||
if (p1->contour == p2->contour)
|
||||
{
|
||||
if ((p1->idx == p2->idx && p1->t == p2->t) ||
|
||||
(p1->idx + 1 == p2->idx && p1->t == 1 && p2->t == 0) ||
|
||||
(p1->idx == p2->idx + 1 && p1->t == 0 && p2->t == 1))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_compare:
|
||||
* @point1: a `GskPathPoint`
|
||||
* @point2: another `GskPathPoint`
|
||||
*
|
||||
* Returns whether @point1 is before or after @point2.
|
||||
*
|
||||
* Returns: -1 if @point1 is before @point2,
|
||||
* 1 if @point1 is after @point2,
|
||||
* 0 if they are equal
|
||||
*/
|
||||
int
|
||||
gsk_path_point_compare (const GskPathPoint *point1,
|
||||
const GskPathPoint *point2)
|
||||
{
|
||||
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
|
||||
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
|
||||
|
||||
if (gsk_path_point_equal (point1, point2))
|
||||
return 0;
|
||||
|
||||
if (p1->contour < p2->contour)
|
||||
return -1;
|
||||
else if (p1->contour > p2->contour)
|
||||
return 1;
|
||||
else if (p1->idx < p2->idx)
|
||||
return -1;
|
||||
else if (p1->idx > p2->idx)
|
||||
return 1;
|
||||
else if (p1->t < p2->t)
|
||||
return -1;
|
||||
else if (p1->t > p2->t)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_position:
|
||||
* @path: a `GskPath`
|
||||
* @point: a `GskPathPoint` on @path
|
||||
* @position: (out caller-allocates): Return location for
|
||||
* the coordinates of the point
|
||||
*
|
||||
* Gets the position of the point.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_point_get_position (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
graphene_point_t *position)
|
||||
{
|
||||
GskRealPathPoint *self = (GskRealPathPoint *) point;
|
||||
const GskContour *contour;
|
||||
|
||||
g_return_if_fail (path != NULL);
|
||||
g_return_if_fail (point != NULL);
|
||||
g_return_if_fail (position != NULL);
|
||||
g_return_if_fail (self->contour < gsk_path_get_n_contours (path));
|
||||
|
||||
contour = gsk_path_get_contour (path, self->contour),
|
||||
gsk_contour_get_position (contour, self, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_tangent:
|
||||
* @path: a `GskPath`
|
||||
* @point: a `GskPathPoint` on @path
|
||||
* @direction: the direction for which to return the tangent
|
||||
* @tangent: (out caller-allocates): Return location for
|
||||
* the tangent at the point
|
||||
*
|
||||
* Gets the tangent of the path at the point.
|
||||
*
|
||||
* Note that certain points on a path may not have a single
|
||||
* tangent, such as sharp turns. At such points, there are
|
||||
* two tangents -- the direction of the path going into the
|
||||
* point, and the direction coming out of it. The @direction
|
||||
* argument lets you choose which one to get.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_point_get_tangent (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
GskRealPathPoint *self = (GskRealPathPoint *) point;
|
||||
const GskContour *contour;
|
||||
|
||||
g_return_if_fail (path != NULL);
|
||||
g_return_if_fail (point != NULL);
|
||||
g_return_if_fail (tangent != NULL);
|
||||
g_return_if_fail (self->contour < gsk_path_get_n_contours (path));
|
||||
|
||||
contour = gsk_path_get_contour (path, self->contour),
|
||||
gsk_contour_get_tangent (contour, self, direction, tangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_curvature:
|
||||
* @path: a `GskPath`
|
||||
* @point: a `GskPathPoint` on @path
|
||||
* @center: (out caller-allocates) (nullable): Return location for
|
||||
* the center of the osculating circle
|
||||
*
|
||||
* Calculates the curvature of the path at the point.
|
||||
*
|
||||
* Optionally, returns the center of the osculating circle as well.
|
||||
*
|
||||
* If the curvature is infinite (at line segments), zero is returned,
|
||||
* and @center is not modified.
|
||||
*
|
||||
* Returns: The curvature of the path at the given point
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
float
|
||||
gsk_path_point_get_curvature (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
graphene_point_t *center)
|
||||
{
|
||||
GskRealPathPoint *self = (GskRealPathPoint *) point;
|
||||
const GskContour *contour;
|
||||
|
||||
g_return_val_if_fail (path != NULL, 0);
|
||||
g_return_val_if_fail (point != NULL, 0);
|
||||
g_return_val_if_fail (self->contour < gsk_path_get_n_contours (path), 0);
|
||||
|
||||
contour = gsk_path_get_contour (path, self->contour);
|
||||
return gsk_contour_get_curvature (contour, self, center);
|
||||
}
|
||||
76
gsk/gskpathpoint.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright © 2023 Red Hat, 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.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_POINT (gsk_path_point_get_type ())
|
||||
|
||||
typedef struct _GskPathPoint GskPathPoint;
|
||||
struct _GskPathPoint {
|
||||
/*< private >*/
|
||||
union {
|
||||
float f[8];
|
||||
gpointer p[8];
|
||||
} data;
|
||||
};
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_path_point_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPathPoint * gsk_path_point_copy (GskPathPoint *point);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_point_free (GskPathPoint *point);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_point_equal (const GskPathPoint *point1,
|
||||
const GskPathPoint *point2);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
int gsk_path_point_compare (const GskPathPoint *point1,
|
||||
const GskPathPoint *point2);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_point_get_position (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
graphene_point_t *position);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_point_get_tangent (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
float gsk_path_point_get_curvature (GskPath *path,
|
||||
const GskPathPoint *point,
|
||||
graphene_point_t *center);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
18
gsk/gskpathpointprivate.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "gskpathpoint.h"
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _GskRealPathPoint
|
||||
{
|
||||
gsize contour;
|
||||
gsize idx;
|
||||
float t;
|
||||
};
|
||||
|
||||
G_STATIC_ASSERT (sizeof (GskRealPathPoint) <= sizeof (GskPathPoint));
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
70
gsk/gskpathprivate.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FLAT,
|
||||
GSK_PATH_CLOSED
|
||||
} GskPathFlags;
|
||||
|
||||
typedef struct _GskContour GskContour;
|
||||
typedef struct _GskRealPathPoint GskRealPathPoint;
|
||||
|
||||
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
|
||||
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
|
||||
|
||||
GskPath * gsk_path_new_from_contours (const GSList *contours);
|
||||
|
||||
gsize gsk_path_get_n_contours (const GskPath *self);
|
||||
const GskContour * gsk_path_get_contour (const GskPath *self,
|
||||
gsize i);
|
||||
|
||||
GskPathFlags gsk_path_get_flags (const GskPath *self);
|
||||
|
||||
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
double tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
void gsk_path_builder_add_contour (GskPathBuilder *builder,
|
||||
GskContour *contour);
|
||||
|
||||
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
|
||||
float rx,
|
||||
float ry,
|
||||
float x_axis_rotation,
|
||||
gboolean large_arc,
|
||||
gboolean positive_sweep,
|
||||
float x,
|
||||
float y);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
20
gsk/gskpointprivate.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <graphene.h>
|
||||
|
||||
static inline void G_GNUC_PURE
|
||||
gsk_point_interpolate (const graphene_point_t *p1,
|
||||
const graphene_point_t *p2,
|
||||
float t,
|
||||
graphene_point_t *p)
|
||||
{
|
||||
p->x = p1->x * (1 - t) + p2->x * t;
|
||||
p->Y = p1->y * (1 - t) + p2->y * t;
|
||||
}
|
||||
|
||||
static inline float G_GNUC_PURE
|
||||
gsk_point_distance (const graphene_point_t *p1,
|
||||
const graphene_point_t *p2)
|
||||
{
|
||||
return sqrtf ((p1->x - p2->x)*(p1->x - p2->x) + (p1->y - p2->y)*(p1->y - p2->y));
|
||||
}
|
||||
@@ -158,6 +158,8 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
|
||||
#define GSK_TYPE_REPEAT_NODE (gsk_repeat_node_get_type())
|
||||
#define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type())
|
||||
#define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type())
|
||||
#define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type())
|
||||
#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type())
|
||||
#define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type())
|
||||
#define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type())
|
||||
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
|
||||
@@ -186,6 +188,8 @@ typedef struct _GskColorMatrixNode GskColorMatrixNode;
|
||||
typedef struct _GskRepeatNode GskRepeatNode;
|
||||
typedef struct _GskClipNode GskClipNode;
|
||||
typedef struct _GskRoundedClipNode GskRoundedClipNode;
|
||||
typedef struct _GskFillNode GskFillNode;
|
||||
typedef struct _GskStrokeNode GskStrokeNode;
|
||||
typedef struct _GskShadowNode GskShadowNode;
|
||||
typedef struct _GskBlendNode GskBlendNode;
|
||||
typedef struct _GskCrossFadeNode GskCrossFadeNode;
|
||||
@@ -459,6 +463,32 @@ GskRenderNode * gsk_rounded_clip_node_get_child (const GskRender
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const GskRoundedRect * gsk_rounded_clip_node_get_clip (const GskRenderNode *node) G_GNUC_PURE;
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_fill_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskRenderNode * gsk_fill_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskRenderNode * gsk_fill_node_get_child (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_fill_node_get_path (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskFillRule gsk_fill_node_get_fill_rule (const GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_stroke_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskRenderNode * gsk_stroke_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskRenderNode * gsk_stroke_node_get_child (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskPath * gsk_stroke_node_get_path (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
const GskStroke * gsk_stroke_node_get_stroke (const GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_shadow_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
|
||||
@@ -25,8 +25,11 @@
|
||||
#include "gskdebugprivate.h"
|
||||
#include "gskdiffprivate.h"
|
||||
#include "gl/gskglrenderer.h"
|
||||
#include "gskpathprivate.h"
|
||||
#include "gskrectprivate.h"
|
||||
#include "gskrendererprivate.h"
|
||||
#include "gskroundedrectprivate.h"
|
||||
#include "gskstrokeprivate.h"
|
||||
#include "gsktransformprivate.h"
|
||||
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
@@ -3967,7 +3970,7 @@ gsk_repeat_node_draw (GskRenderNode *node,
|
||||
scale_x *= width / self->child_bounds.size.width;
|
||||
scale_y *= height / self->child_bounds.size.height;
|
||||
cairo_surface_set_device_scale (surface, scale_x, scale_y);
|
||||
cairo_surface_set_device_offset (surface,
|
||||
cairo_surface_set_device_offset (surface,
|
||||
- self->child_bounds.origin.x * scale_x,
|
||||
- self->child_bounds.origin.y * scale_y);
|
||||
|
||||
@@ -4138,7 +4141,7 @@ gsk_clip_node_diff (GskRenderNode *node1,
|
||||
gsk_render_node_diff_impossible (node1, node2, region);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gsk_clip_node_class_init (gpointer g_class,
|
||||
gpointer class_data)
|
||||
@@ -4366,6 +4369,423 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
|
||||
return &self->clip;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_FILL_NODE */
|
||||
|
||||
/**
|
||||
* GskFillNode:
|
||||
*
|
||||
* A render node filling the area given by [struct@Gsk.Path]
|
||||
* and [enum@Gsk.FillRule] with the child node.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
|
||||
struct _GskFillNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *child;
|
||||
GskPath *path;
|
||||
GskFillRule fill_rule;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_fill_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskFillNode *self = (GskFillNode *) node;
|
||||
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
|
||||
|
||||
gsk_render_node_unref (self->child);
|
||||
gsk_path_unref (self->path);
|
||||
|
||||
parent_class->finalize (node);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_draw (GskRenderNode *node,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GskFillNode *self = (GskFillNode *) node;
|
||||
|
||||
cairo_save (cr);
|
||||
|
||||
switch (self->fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
|
||||
break;
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
gsk_path_to_cairo (self->path, cr);
|
||||
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
|
||||
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
|
||||
{
|
||||
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
|
||||
cairo_fill (cr);
|
||||
}
|
||||
else
|
||||
{
|
||||
cairo_clip (cr);
|
||||
gsk_render_node_draw (self->child, cr);
|
||||
}
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_diff (GskRenderNode *node1,
|
||||
GskRenderNode *node2,
|
||||
cairo_region_t *region)
|
||||
{
|
||||
GskFillNode *self1 = (GskFillNode *) node1;
|
||||
GskFillNode *self2 = (GskFillNode *) node2;
|
||||
|
||||
if (self1->path == self2->path)
|
||||
{
|
||||
cairo_region_t *sub;
|
||||
cairo_rectangle_int_t clip_rect;
|
||||
|
||||
sub = cairo_region_create();
|
||||
gsk_render_node_diff (self1->child, self2->child, sub);
|
||||
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
|
||||
cairo_region_intersect_rectangle (sub, &clip_rect);
|
||||
cairo_region_union (region, sub);
|
||||
cairo_region_destroy (sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsk_render_node_diff_impossible (node1, node2, region);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_class_init (gpointer g_class,
|
||||
gpointer class_data)
|
||||
{
|
||||
GskRenderNodeClass *node_class = g_class;
|
||||
|
||||
node_class->node_type = GSK_FILL_NODE;
|
||||
|
||||
node_class->finalize = gsk_fill_node_finalize;
|
||||
node_class->draw = gsk_fill_node_draw;
|
||||
node_class->diff = gsk_fill_node_diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_new:
|
||||
* @child: The node to fill the area with
|
||||
* @path: The path describing the area to fill
|
||||
* @fill_rule: The fill rule to use
|
||||
*
|
||||
* Creates a `GskRenderNode` that will fill the @child in the area
|
||||
* given by @path and @fill_rule.
|
||||
*
|
||||
* Returns: (transfer none) (type GskFillNode): A new `GskRenderNode`
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_fill_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
GskFillNode *self;
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t path_bounds;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
|
||||
self = gsk_render_node_alloc (GSK_FILL_NODE);
|
||||
node = (GskRenderNode *) self;
|
||||
|
||||
self->child = gsk_render_node_ref (child);
|
||||
self->path = gsk_path_ref (path);
|
||||
self->fill_rule = fill_rule;
|
||||
|
||||
if (gsk_path_get_bounds (path, &path_bounds))
|
||||
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
|
||||
else
|
||||
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_child:
|
||||
* @node: (type GskFillNode): a fill `GskRenderNode`
|
||||
*
|
||||
* Gets the child node that is getting drawn by the given @node.
|
||||
*
|
||||
* Returns: (transfer none): The child that is getting drawn
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_fill_node_get_child (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
||||
|
||||
return self->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_path:
|
||||
* @node: (type GskFillNode): a fill `GskRenderNode`
|
||||
*
|
||||
* Retrieves the path used to describe the area filled with the contents of
|
||||
* the @node.
|
||||
*
|
||||
* Returns: (transfer none): a `GskPath`
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskPath *
|
||||
gsk_fill_node_get_path (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
||||
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_fill_rule:
|
||||
* @node: (type GskFillNode): a fill `GskRenderNode`
|
||||
*
|
||||
* Retrieves the fill rule used to determine how the path is filled.
|
||||
*
|
||||
* Returns: a `GskFillRule`
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskFillRule
|
||||
gsk_fill_node_get_fill_rule (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
|
||||
|
||||
return self->fill_rule;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_STROKE_NODE */
|
||||
|
||||
/**
|
||||
* GskStrokeNode:
|
||||
*
|
||||
* A render node that will fill the area determined by stroking the the given
|
||||
* [struct@Gsk.Path] using the [struct@Gsk.Stroke] attributes.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
|
||||
struct _GskStrokeNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *child;
|
||||
GskPath *path;
|
||||
GskStroke stroke;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_stroke_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskStrokeNode *self = (GskStrokeNode *) node;
|
||||
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
|
||||
|
||||
gsk_render_node_unref (self->child);
|
||||
gsk_path_unref (self->path);
|
||||
gsk_stroke_clear (&self->stroke);
|
||||
|
||||
parent_class->finalize (node);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_draw (GskRenderNode *node,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GskStrokeNode *self = (GskStrokeNode *) node;
|
||||
|
||||
cairo_save (cr);
|
||||
|
||||
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
|
||||
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
|
||||
{
|
||||
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
|
||||
}
|
||||
else
|
||||
{
|
||||
gsk_cairo_rectangle (cr, &self->child->bounds);
|
||||
cairo_clip (cr);
|
||||
cairo_push_group (cr);
|
||||
gsk_render_node_draw (self->child, cr);
|
||||
cairo_pop_group_to_source (cr);
|
||||
}
|
||||
|
||||
gsk_stroke_to_cairo (&self->stroke, cr);
|
||||
|
||||
gsk_path_to_cairo (self->path, cr);
|
||||
cairo_stroke (cr);
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_diff (GskRenderNode *node1,
|
||||
GskRenderNode *node2,
|
||||
cairo_region_t *region)
|
||||
{
|
||||
GskStrokeNode *self1 = (GskStrokeNode *) node1;
|
||||
GskStrokeNode *self2 = (GskStrokeNode *) node2;
|
||||
|
||||
if (self1->path == self2->path &&
|
||||
gsk_stroke_equal (&self1->stroke, &self2->stroke))
|
||||
{
|
||||
cairo_region_t *sub;
|
||||
cairo_rectangle_int_t clip_rect;
|
||||
|
||||
sub = cairo_region_create();
|
||||
gsk_render_node_diff (self1->child, self2->child, sub);
|
||||
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
|
||||
cairo_region_intersect_rectangle (sub, &clip_rect);
|
||||
cairo_region_union (region, sub);
|
||||
cairo_region_destroy (sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsk_render_node_diff_impossible (node1, node2, region);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_class_init (gpointer g_class,
|
||||
gpointer class_data)
|
||||
{
|
||||
GskRenderNodeClass *node_class = g_class;
|
||||
|
||||
node_class->node_type = GSK_STROKE_NODE;
|
||||
|
||||
node_class->finalize = gsk_stroke_node_finalize;
|
||||
node_class->draw = gsk_stroke_node_draw;
|
||||
node_class->diff = gsk_stroke_node_diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_new:
|
||||
* @child: The node to stroke the area with
|
||||
* @path: (transfer none): The path describing the area to stroke
|
||||
* @stroke: (transfer none): The stroke attributes to use
|
||||
*
|
||||
* Creates a #GskRenderNode that will fill the outline generated by stroking
|
||||
* the given @path using the attributes defined in @stroke.
|
||||
*
|
||||
* The area is filled with @child.
|
||||
*
|
||||
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_stroke_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke)
|
||||
{
|
||||
GskStrokeNode *self;
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t stroke_bounds;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
g_return_val_if_fail (stroke != NULL, NULL);
|
||||
|
||||
self = gsk_render_node_alloc (GSK_STROKE_NODE);
|
||||
node = (GskRenderNode *) self;
|
||||
|
||||
self->child = gsk_render_node_ref (child);
|
||||
self->path = gsk_path_ref (path);
|
||||
gsk_stroke_init_copy (&self->stroke, stroke);
|
||||
|
||||
if (gsk_path_get_stroke_bounds (self->path, &self->stroke, &stroke_bounds))
|
||||
graphene_rect_intersection (&stroke_bounds, &child->bounds, &node->bounds);
|
||||
else
|
||||
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_child:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Gets the child node that is getting drawn by the given @node.
|
||||
*
|
||||
* Returns: (transfer none): The child that is getting drawn
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_stroke_node_get_child (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return self->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_path:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Retrieves the path that will be stroked with the contents of
|
||||
* the @node.
|
||||
*
|
||||
* Returns: (transfer none): a #GskPath
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskPath *
|
||||
gsk_stroke_node_get_path (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_stroke:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Retrieves the stroke attributes used in this @node.
|
||||
*
|
||||
* Returns: a #GskStroke
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
const GskStroke *
|
||||
gsk_stroke_node_get_stroke (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return &self->stroke;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_SHADOW_NODE */
|
||||
|
||||
@@ -6259,6 +6679,8 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
|
||||
@@ -6407,6 +6829,16 @@ gsk_render_node_init_types_once (void)
|
||||
sizeof (GskDebugNode),
|
||||
gsk_debug_node_class_init);
|
||||
gsk_render_node_types[GSK_DEBUG_NODE] = node_type;
|
||||
|
||||
node_type = gsk_render_node_type_register_static (I_("GskFillNode"),
|
||||
sizeof (GskFillNode),
|
||||
gsk_fill_node_class_init);
|
||||
gsk_render_node_types[GSK_FILL_NODE] = node_type;
|
||||
|
||||
node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"),
|
||||
sizeof (GskStrokeNode),
|
||||
gsk_stroke_node_class_init);
|
||||
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -23,9 +23,13 @@
|
||||
|
||||
#include "gskrendernodeparserprivate.h"
|
||||
|
||||
#include "gskpath.h"
|
||||
#include "gskpathbuilder.h"
|
||||
#include "gskroundedrectprivate.h"
|
||||
#include "gskrendernodeprivate.h"
|
||||
#include "gskstroke.h"
|
||||
#include "gsktransformprivate.h"
|
||||
#include "gskenumtypes.h"
|
||||
|
||||
#include "gdk/gdkrgbaprivate.h"
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
@@ -1174,6 +1178,26 @@ create_default_render_node (void)
|
||||
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_default_path (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_builder_move_to (builder, 25, 0);
|
||||
for (i = 1; i < 5; i++)
|
||||
{
|
||||
gsk_path_builder_line_to (builder,
|
||||
sin (i * G_PI * 0.8) * 25 + 25,
|
||||
-cos (i * G_PI * 0.8) * 25 + 25);
|
||||
}
|
||||
gsk_path_builder_close (builder);
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_color_node (GtkCssParser *parser,
|
||||
Context *context)
|
||||
@@ -2097,6 +2121,165 @@ parse_rounded_clip_node (GtkCssParser *parser,
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_path (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out_path)
|
||||
{
|
||||
GskPath *path;
|
||||
char *str = NULL;
|
||||
|
||||
if (!parse_string (parser, context, &str))
|
||||
return FALSE;
|
||||
|
||||
path = gsk_path_parse (str);
|
||||
g_free (str);
|
||||
|
||||
if (path == NULL)
|
||||
{
|
||||
gtk_css_parser_error_value (parser, "Invalid path");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*((GskPath **) out_path) = path;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
clear_path (gpointer inout_path)
|
||||
{
|
||||
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_enum (GtkCssParser *parser,
|
||||
GType type,
|
||||
gpointer out_value)
|
||||
{
|
||||
GEnumClass *class;
|
||||
GEnumValue *v;
|
||||
char *enum_name;
|
||||
|
||||
enum_name = gtk_css_parser_consume_ident (parser);
|
||||
if (enum_name == NULL)
|
||||
return FALSE;
|
||||
|
||||
class = g_type_class_ref (type);
|
||||
|
||||
v = g_enum_get_value_by_nick (class, enum_name);
|
||||
if (v == NULL)
|
||||
{
|
||||
gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"",
|
||||
enum_name, g_type_name (type));
|
||||
g_free (enum_name);
|
||||
g_type_class_unref (class);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*(int*)out_value = v->value;
|
||||
|
||||
g_free (enum_name);
|
||||
g_type_class_unref (class);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_fill_rule (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out_rule)
|
||||
{
|
||||
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_fill_node (GtkCssParser *parser,
|
||||
Context *context)
|
||||
{
|
||||
GskRenderNode *child = NULL;
|
||||
GskPath *path = NULL;
|
||||
int rule = GSK_FILL_RULE_WINDING;
|
||||
const Declaration declarations[] = {
|
||||
{ "child", parse_node, clear_node, &child },
|
||||
{ "path", parse_path, clear_path, &path },
|
||||
{ "fill-rule", parse_fill_rule, NULL, &rule },
|
||||
};
|
||||
GskRenderNode *result;
|
||||
|
||||
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
||||
if (child == NULL)
|
||||
child = create_default_render_node ();
|
||||
if (path == NULL)
|
||||
path = create_default_path ();
|
||||
|
||||
result = gsk_fill_node_new (child, path, rule);
|
||||
|
||||
gsk_path_unref (path);
|
||||
|
||||
gsk_render_node_unref (child);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_line_cap (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out)
|
||||
{
|
||||
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_line_join (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out)
|
||||
{
|
||||
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_stroke_node (GtkCssParser *parser,
|
||||
Context *context)
|
||||
{
|
||||
GskRenderNode *child = NULL;
|
||||
GskPath *path = NULL;
|
||||
double line_width = 1.0;
|
||||
int line_cap = GSK_LINE_CAP_BUTT;
|
||||
int line_join = GSK_LINE_JOIN_MITER;
|
||||
double miter_limit = 4.0;
|
||||
GskStroke *stroke;
|
||||
|
||||
const Declaration declarations[] = {
|
||||
{ "child", parse_node, clear_node, &child },
|
||||
{ "path", parse_path, clear_path, &path },
|
||||
{ "line-width", parse_positive_double, NULL, &line_width },
|
||||
{ "line-cap", parse_line_cap, NULL, &line_cap },
|
||||
{ "line-join", parse_line_join, NULL, &line_join },
|
||||
{ "miter-limit", parse_positive_double, NULL, &miter_limit }
|
||||
};
|
||||
GskRenderNode *result;
|
||||
|
||||
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
|
||||
if (child == NULL)
|
||||
child = create_default_render_node ();
|
||||
if (path == NULL)
|
||||
path = create_default_path ();
|
||||
|
||||
stroke = gsk_stroke_new (line_width);
|
||||
gsk_stroke_set_line_cap (stroke, line_cap);
|
||||
gsk_stroke_set_line_join (stroke, line_join);
|
||||
gsk_stroke_set_miter_limit (stroke, miter_limit);
|
||||
|
||||
result = gsk_stroke_node_new (child, path, stroke);
|
||||
|
||||
gsk_path_unref (path);
|
||||
gsk_stroke_free (stroke);
|
||||
gsk_render_node_unref (child);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_shadow_node (GtkCssParser *parser,
|
||||
Context *context)
|
||||
@@ -2179,6 +2362,8 @@ parse_node (GtkCssParser *parser,
|
||||
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
|
||||
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
|
||||
{ "rounded-clip", parse_rounded_clip_node },
|
||||
{ "fill", parse_fill_node },
|
||||
{ "stroke", parse_stroke_node },
|
||||
{ "shadow", parse_shadow_node },
|
||||
{ "text", parse_text_node },
|
||||
{ "texture", parse_texture_node },
|
||||
@@ -2433,6 +2618,14 @@ printer_init_duplicates_for_node (Printer *printer,
|
||||
printer_init_duplicates_for_node (printer, gsk_debug_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_fill_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_stroke_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_BLEND_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_blend_node_get_bottom_child (node));
|
||||
printer_init_duplicates_for_node (printer, gsk_blend_node_get_top_child (node));
|
||||
@@ -2658,7 +2851,7 @@ append_float_param (Printer *p,
|
||||
float value,
|
||||
float default_value)
|
||||
{
|
||||
/* Don't approximate-compare here, better be topo verbose */
|
||||
/* Don't approximate-compare here, better be too verbose */
|
||||
if (value == default_value)
|
||||
return;
|
||||
|
||||
@@ -2833,8 +3026,11 @@ append_escaping_newlines (GString *str,
|
||||
len = strcspn (string, "\n");
|
||||
g_string_append_len (str, string, len);
|
||||
string += len;
|
||||
g_string_append (str, "\\\n");
|
||||
string++;
|
||||
if (*string)
|
||||
{
|
||||
g_string_append (str, "\\\n");
|
||||
string++;
|
||||
}
|
||||
} while (*string);
|
||||
}
|
||||
|
||||
@@ -3035,6 +3231,55 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
|
||||
pango_glyph_string_free (ascii);
|
||||
}
|
||||
|
||||
static const char *
|
||||
enum_to_nick (GType type,
|
||||
int value)
|
||||
{
|
||||
GEnumClass *class;
|
||||
GEnumValue *v;
|
||||
|
||||
class = g_type_class_ref (type);
|
||||
v = g_enum_get_value (class, value);
|
||||
g_type_class_unref (class);
|
||||
|
||||
return v->value_nick;
|
||||
}
|
||||
|
||||
static void
|
||||
append_enum_param (Printer *p,
|
||||
const char *param_name,
|
||||
GType type,
|
||||
int value)
|
||||
{
|
||||
_indent (p);
|
||||
g_string_append_printf (p->str, "%s: ", param_name);
|
||||
g_string_append (p->str, enum_to_nick (type, value));
|
||||
g_string_append_c (p->str, ';');
|
||||
g_string_append_c (p->str, '\n');
|
||||
}
|
||||
|
||||
static void
|
||||
append_path_param (Printer *p,
|
||||
const char *param_name,
|
||||
GskPath *path)
|
||||
{
|
||||
char *str, *s;
|
||||
|
||||
_indent (p);
|
||||
g_string_append (p->str, "path: \"\\\n");
|
||||
str = gsk_path_to_string (path);
|
||||
/* Put each command on a new line */
|
||||
for (s = str; *s; s++)
|
||||
{
|
||||
if (*s == ' ' &&
|
||||
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
|
||||
*s = '\n';
|
||||
}
|
||||
append_escaping_newlines (p->str, str);
|
||||
g_string_append (p->str, "\";\n");
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
static void
|
||||
render_node_print (Printer *p,
|
||||
GskRenderNode *node)
|
||||
@@ -3204,6 +3449,36 @@ render_node_print (Printer *p,
|
||||
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
|
||||
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
{
|
||||
start_node (p, "fill", node_name);
|
||||
|
||||
append_node_param (p, "child", gsk_fill_node_get_child (node));
|
||||
append_path_param (p, "path", gsk_fill_node_get_path (node));
|
||||
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
{
|
||||
const GskStroke *stroke;
|
||||
|
||||
start_node (p, "stroke", node_name);
|
||||
|
||||
append_node_param (p, "child", gsk_stroke_node_get_child (node));
|
||||
append_path_param (p, "path", gsk_stroke_node_get_path (node));
|
||||
|
||||
stroke = gsk_stroke_node_get_stroke (node);
|
||||
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
|
||||
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
|
||||
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
|
||||
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ typedef struct _GskRenderNodeClass GskRenderNodeClass;
|
||||
* We don't add an "n-types" value to avoid having to handle
|
||||
* it in every single switch.
|
||||
*/
|
||||
#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_MASK_NODE + 1)
|
||||
#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_STROKE_NODE + 1)
|
||||
|
||||
extern GType gsk_render_node_types[];
|
||||
|
||||
|
||||
208
gsk/gskspline.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright © 2002 University of Southern California
|
||||
* 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
* Carl D. Worth <cworth@cworth.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gsksplineprivate.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* Spline deviation from the circle in radius would be given by:
|
||||
|
||||
error = sqrt (x**2 + y**2) - 1
|
||||
|
||||
A simpler error function to work with is:
|
||||
|
||||
e = x**2 + y**2 - 1
|
||||
|
||||
From "Good approximation of circles by curvature-continuous Bezier
|
||||
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
|
||||
Design 8 (1990) 22-41, we learn:
|
||||
|
||||
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
|
||||
|
||||
and
|
||||
abs (error) =~ 1/2 * e
|
||||
|
||||
Of course, this error value applies only for the particular spline
|
||||
approximation that is used in _cairo_gstate_arc_segment.
|
||||
*/
|
||||
static float
|
||||
arc_error_normalized (float angle)
|
||||
{
|
||||
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
|
||||
}
|
||||
|
||||
static float
|
||||
arc_max_angle_for_tolerance_normalized (float tolerance)
|
||||
{
|
||||
float angle, error;
|
||||
guint i;
|
||||
|
||||
/* Use table lookup to reduce search time in most cases. */
|
||||
struct {
|
||||
float angle;
|
||||
float error;
|
||||
} table[] = {
|
||||
{ G_PI / 1.0, 0.0185185185185185036127 },
|
||||
{ G_PI / 2.0, 0.000272567143730179811158 },
|
||||
{ G_PI / 3.0, 2.38647043651461047433e-05 },
|
||||
{ G_PI / 4.0, 4.2455377443222443279e-06 },
|
||||
{ G_PI / 5.0, 1.11281001494389081528e-06 },
|
||||
{ G_PI / 6.0, 3.72662000942734705475e-07 },
|
||||
{ G_PI / 7.0, 1.47783685574284411325e-07 },
|
||||
{ G_PI / 8.0, 6.63240432022601149057e-08 },
|
||||
{ G_PI / 9.0, 3.2715520137536980553e-08 },
|
||||
{ G_PI / 10.0, 1.73863223499021216974e-08 },
|
||||
{ G_PI / 11.0, 9.81410988043554039085e-09 },
|
||||
};
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (table); i++)
|
||||
{
|
||||
if (table[i].error < tolerance)
|
||||
return table[i].angle;
|
||||
}
|
||||
|
||||
i++;
|
||||
do {
|
||||
angle = G_PI / i++;
|
||||
error = arc_error_normalized (angle);
|
||||
} while (error > tolerance);
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
static guint
|
||||
arc_segments_needed (float angle,
|
||||
float radius,
|
||||
float tolerance)
|
||||
{
|
||||
float max_angle;
|
||||
|
||||
/* the error is amplified by at most the length of the
|
||||
* major axis of the circle; see cairo-pen.c for a more detailed analysis
|
||||
* of this. */
|
||||
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
|
||||
|
||||
return ceil (fabs (angle) / max_angle);
|
||||
}
|
||||
|
||||
/* We want to draw a single spline approximating a circular arc radius
|
||||
R from angle A to angle B. Since we want a symmetric spline that
|
||||
matches the endpoints of the arc in position and slope, we know
|
||||
that the spline control points must be:
|
||||
|
||||
(R * cos(A), R * sin(A))
|
||||
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
|
||||
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
|
||||
(R * cos(B), R * sin(B))
|
||||
|
||||
for some value of h.
|
||||
|
||||
"Approximation of circular arcs by cubic polynomials", Michael
|
||||
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
|
||||
various values of h along with error analysis for each.
|
||||
|
||||
From that paper, a very practical value of h is:
|
||||
|
||||
h = 4/3 * R * tan(angle/4)
|
||||
|
||||
This value does not give the spline with minimal error, but it does
|
||||
provide a very good approximation, (6th-order convergence), and the
|
||||
error expression is quite simple, (see the comment for
|
||||
_arc_error_normalized).
|
||||
*/
|
||||
static gboolean
|
||||
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
|
||||
float radius,
|
||||
float angle_A,
|
||||
float angle_B,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float r_sin_A, r_cos_A;
|
||||
float r_sin_B, r_cos_B;
|
||||
float h;
|
||||
|
||||
r_sin_A = radius * sin (angle_A);
|
||||
r_cos_A = radius * cos (angle_A);
|
||||
r_sin_B = radius * sin (angle_B);
|
||||
r_cos_B = radius * cos (angle_B);
|
||||
|
||||
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
|
||||
|
||||
return curve_func ((graphene_point_t[4]) {
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A,
|
||||
center->y + r_sin_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A - h * r_sin_A,
|
||||
center->y + r_sin_A + h * r_cos_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B + h * r_sin_B,
|
||||
center->y + r_sin_B - h * r_cos_B
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B,
|
||||
center->y + r_sin_B
|
||||
)
|
||||
},
|
||||
user_data);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float step = start_angle - end_angle;
|
||||
guint i, n_segments;
|
||||
|
||||
/* Recurse if drawing arc larger than pi */
|
||||
if (ABS (step) > G_PI)
|
||||
{
|
||||
float mid_angle = (start_angle + end_angle) / 2.0;
|
||||
|
||||
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
|
||||
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
else if (ABS (step) < tolerance)
|
||||
{
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
||||
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
|
||||
step = (end_angle - start_angle) / n_segments;
|
||||
|
||||
for (i = 0; i < n_segments - 1; i++, start_angle += step)
|
||||
{
|
||||
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
||||
41
gsk/gsksplineprivate.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_SPLINE_PRIVATE_H__
|
||||
#define __GSK_SPLINE_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
|
||||
gpointer user_data);
|
||||
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_SPLINE_PRIVATE_H__ */
|
||||
|
||||
537
gsk/gskstroke.c
Normal file
@@ -0,0 +1,537 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gskstrokeprivate.h"
|
||||
|
||||
/**
|
||||
* GskStroke:
|
||||
*
|
||||
* A `GskStroke` struct collects the parameters that influence
|
||||
* the operation of stroking a path.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
|
||||
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke, gsk_stroke_copy, gsk_stroke_free)
|
||||
|
||||
|
||||
/**
|
||||
* gsk_stroke_new:
|
||||
* @line_width: line width of the stroke. Must be > 0
|
||||
*
|
||||
* Creates a new `GskStroke` with the given @line_width.
|
||||
*
|
||||
* Returns: a new `GskStroke`
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskStroke *
|
||||
gsk_stroke_new (float line_width)
|
||||
{
|
||||
GskStroke *self;
|
||||
|
||||
g_return_val_if_fail (line_width > 0, NULL);
|
||||
|
||||
self = g_new0 (GskStroke, 1);
|
||||
|
||||
self->line_width = line_width;
|
||||
self->line_cap = GSK_LINE_CAP_BUTT;
|
||||
self->line_join = GSK_LINE_JOIN_MITER;
|
||||
self->miter_limit = 4.f; /* following svg */
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_copy:
|
||||
* @other: `GskStroke` to copy
|
||||
*
|
||||
* Creates a copy of the given @other stroke.
|
||||
*
|
||||
* Returns: a new `GskStroke`. Use [method@Gsk.Stroke.free] to free it
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskStroke *
|
||||
gsk_stroke_copy (const GskStroke *other)
|
||||
{
|
||||
GskStroke *self;
|
||||
|
||||
g_return_val_if_fail (other != NULL, NULL);
|
||||
|
||||
self = g_new (GskStroke, 1);
|
||||
|
||||
gsk_stroke_init_copy (self, other);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_free:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Frees a `GskStroke`.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_free (GskStroke *self)
|
||||
{
|
||||
if (self == NULL)
|
||||
return;
|
||||
|
||||
gsk_stroke_clear (self);
|
||||
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_to_cairo:
|
||||
* @self: a `GskStroke`
|
||||
* @cr: the cairo context to configure
|
||||
*
|
||||
* A helper function that sets the stroke parameters
|
||||
* of @cr from the values found in @self.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_to_cairo (const GskStroke *self,
|
||||
cairo_t *cr)
|
||||
{
|
||||
cairo_set_line_width (cr, self->line_width);
|
||||
|
||||
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
||||
switch (self->line_cap)
|
||||
{
|
||||
case GSK_LINE_CAP_BUTT:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
|
||||
break;
|
||||
case GSK_LINE_CAP_ROUND:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
||||
break;
|
||||
case GSK_LINE_CAP_SQUARE:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
||||
switch (self->line_join)
|
||||
{
|
||||
case GSK_LINE_JOIN_MITER:
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
|
||||
break;
|
||||
case GSK_LINE_JOIN_ROUND:
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
|
||||
break;
|
||||
case GSK_LINE_JOIN_BEVEL:
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
cairo_set_miter_limit (cr, self->miter_limit);
|
||||
|
||||
if (self->dash_length)
|
||||
{
|
||||
gsize i;
|
||||
double *dash = g_newa (double, self->n_dash);
|
||||
|
||||
for (i = 0; i < self->n_dash; i++)
|
||||
{
|
||||
dash[i] = self->dash[i];
|
||||
}
|
||||
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
|
||||
}
|
||||
else
|
||||
cairo_set_dash (cr, NULL, 0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_equal:
|
||||
* @stroke1: the first `GskStroke`
|
||||
* @stroke2: the second `GskStroke`
|
||||
*
|
||||
* Checks if 2 strokes are identical.
|
||||
*
|
||||
* Returns: `TRUE` if the 2 strokes are equal, `FALSE` otherwise
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
gboolean
|
||||
gsk_stroke_equal (gconstpointer stroke1,
|
||||
gconstpointer stroke2)
|
||||
{
|
||||
const GskStroke *self1 = stroke1;
|
||||
const GskStroke *self2 = stroke2;
|
||||
|
||||
if (self1->line_width != self2->line_width ||
|
||||
self1->line_cap != self2->line_cap ||
|
||||
self1->line_join != self2->line_join ||
|
||||
self1->miter_limit != self2->miter_limit ||
|
||||
self1->n_dash != self2->n_dash ||
|
||||
self1->dash_offset != self2->dash_offset)
|
||||
return FALSE;
|
||||
|
||||
for (gsize i = 0; i < self1->n_dash; i++)
|
||||
if (self1->dash[i] != self2->dash[i])
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_width:
|
||||
* @self: a `GskStroke`
|
||||
* @line_width: width of the line in pixels
|
||||
*
|
||||
* Sets the line width to be used when stroking.
|
||||
*
|
||||
* The line width must be > 0.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_width (GskStroke *self,
|
||||
float line_width)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (line_width > 0);
|
||||
|
||||
self->line_width = line_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_width:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line width used.
|
||||
*
|
||||
* Returns: The line width
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_line_width (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_cap:
|
||||
* @self: a`GskStroke`
|
||||
* @line_cap: the `GskLineCap`
|
||||
*
|
||||
* Sets the line cap to be used when stroking.
|
||||
*
|
||||
* See [enum@Gsk.LineCap] for details.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_cap (GskStroke *self,
|
||||
GskLineCap line_cap)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->line_cap = line_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_cap:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line cap used.
|
||||
*
|
||||
* See [enum@Gsk.LineCap] for details.
|
||||
*
|
||||
* Returns: The line cap
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskLineCap
|
||||
gsk_stroke_get_line_cap (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_join:
|
||||
* @self: a `GskStroke`
|
||||
* @line_join: The line join to use
|
||||
*
|
||||
* Sets the line join to be used when stroking.
|
||||
*
|
||||
* See [enum@Gsk.LineJoin] for details.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_join (GskStroke *self,
|
||||
GskLineJoin line_join)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->line_join = line_join;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_join:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line join used.
|
||||
*
|
||||
* See [enum@Gsk.LineJoin] for details.
|
||||
*
|
||||
* Returns: The line join
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
GskLineJoin
|
||||
gsk_stroke_get_line_join (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_join;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_miter_limit:
|
||||
* @self: a `GskStroke`
|
||||
* @limit: the miter limit
|
||||
*
|
||||
* Sets the limit for the distance from the corner where sharp
|
||||
* turns of joins get cut off.
|
||||
*
|
||||
* The miter limit is in units of line width and must be non-negative.
|
||||
*
|
||||
* For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter
|
||||
* limit, the join gets rendered as if it was of type
|
||||
* `GSK_LINE_JOIN_BEVEL`.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_miter_limit (GskStroke *self,
|
||||
float limit)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (limit >= 0);
|
||||
|
||||
self->miter_limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_miter_limit:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Returns the miter limit of a `GskStroke`.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_miter_limit (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 4.f);
|
||||
|
||||
return self->miter_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_dash:
|
||||
* @self: a `GskStroke`
|
||||
* @dash: (array length=n_dash) (transfer none) (nullable):
|
||||
* the array of dashes
|
||||
* @n_dash: number of elements in @dash
|
||||
*
|
||||
* Sets the dash pattern to use by this stroke.
|
||||
*
|
||||
* A dash pattern is specified by an array of alternating non-negative
|
||||
* values. Each value provides the length of alternate "on" and "off"
|
||||
* portions of the stroke.
|
||||
*
|
||||
* Each "on" segment will have caps applied as if the segment were a
|
||||
* separate contour. In particular, it is valid to use an "on" length
|
||||
* of 0 with `GSK_LINE_CAP_ROUND` or `GSK_LINE_CAP_SQUARE` to draw dots
|
||||
* or squares along a path.
|
||||
*
|
||||
* If @n_dash is 0, if all elements in @dash are 0, or if there are
|
||||
* negative values in @dash, then dashing is disabled.
|
||||
*
|
||||
* If @n_dash is 1, an alternating "on" and "off" pattern with the
|
||||
* single dash length provided is assumed.
|
||||
*
|
||||
* If @n_dash is uneven, the dash array will be used with the first
|
||||
* element in @dash defining an "on" or "off" in alternating passes
|
||||
* through the array.
|
||||
*
|
||||
* You can specify a starting offset into the dash with
|
||||
* [method@Gsk.Stroke.set_dash_offset].
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_dash (GskStroke *self,
|
||||
const float *dash,
|
||||
gsize n_dash)
|
||||
{
|
||||
float dash_length;
|
||||
gsize i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (dash != NULL || n_dash == 0);
|
||||
|
||||
dash_length = 0;
|
||||
for (i = 0; i < n_dash; i++)
|
||||
{
|
||||
if (!(dash[i] >= 0)) /* should catch NaN */
|
||||
{
|
||||
g_critical ("invalid value in dash array at position %zu", i);
|
||||
return;
|
||||
}
|
||||
dash_length += dash[i];
|
||||
}
|
||||
|
||||
self->dash_length = dash_length;
|
||||
g_free (self->dash);
|
||||
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
|
||||
self->n_dash = n_dash;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_dash:
|
||||
* @self: a `GskStroke`
|
||||
* @n_dash: (out caller-allocates): number of elements
|
||||
* in the array returned
|
||||
*
|
||||
* Gets the dash array in use or `NULL` if dashing is disabled.
|
||||
*
|
||||
* Returns: (array length=n_dash) (transfer none) (nullable):
|
||||
* The dash array or `NULL` if the dash array is empty.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
const float *
|
||||
gsk_stroke_get_dash (const GskStroke *self,
|
||||
gsize *n_dash)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (n_dash != NULL, NULL);
|
||||
|
||||
*n_dash = self->n_dash;
|
||||
|
||||
return self->dash;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_dash_offset:
|
||||
* @self: a `GskStroke`
|
||||
* @offset: offset into the dash pattern
|
||||
*
|
||||
* Sets the offset into the dash pattern where dashing should begin.
|
||||
*
|
||||
* This is an offset into the length of the path, not an index into
|
||||
* the array values of the dash array.
|
||||
*
|
||||
* See [method@Gsk.Stroke.set_dash] for more details on dashing.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_dash_offset (GskStroke *self,
|
||||
float offset)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->dash_offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_dash_offset:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Returns the dash_offset of a `GskStroke`.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_dash_offset (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 4.f);
|
||||
|
||||
return self->dash_offset;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_stroke_get_join_width:
|
||||
* @stroke: a `GskStroke`
|
||||
*
|
||||
* Return a width that is sufficient to use
|
||||
* when calculating stroke bounds around joins
|
||||
* and caps.
|
||||
*
|
||||
* Returns: the join width
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_join_width (const GskStroke *stroke)
|
||||
{
|
||||
float width;
|
||||
|
||||
switch (stroke->line_cap)
|
||||
{
|
||||
case GSK_LINE_CAP_BUTT:
|
||||
width = 0;
|
||||
break;
|
||||
case GSK_LINE_CAP_ROUND:
|
||||
width = stroke->line_width;
|
||||
break;
|
||||
case GSK_LINE_CAP_SQUARE:
|
||||
width = G_SQRT2 * stroke->line_width;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
switch (stroke->line_join)
|
||||
{
|
||||
case GSK_LINE_JOIN_MITER:
|
||||
width = MAX (width, MAX (stroke->miter_limit, 1.f) * stroke->line_width);
|
||||
break;
|
||||
case GSK_LINE_JOIN_ROUND:
|
||||
case GSK_LINE_JOIN_BEVEL:
|
||||
width = MAX (width, stroke->line_width);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
87
gsk/gskstroke.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_STROKE (gsk_stroke_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GType gsk_stroke_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskStroke * gsk_stroke_new (float line_width);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskStroke * gsk_stroke_copy (const GskStroke *other);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_free (GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_stroke_equal (gconstpointer stroke1,
|
||||
gconstpointer stroke2);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_line_width (GskStroke *self,
|
||||
float line_width);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
float gsk_stroke_get_line_width (const GskStroke *self);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_line_cap (GskStroke *self,
|
||||
GskLineCap line_cap);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskLineCap gsk_stroke_get_line_cap (const GskStroke *self);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_line_join (GskStroke *self,
|
||||
GskLineJoin line_join);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
GskLineJoin gsk_stroke_get_line_join (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_miter_limit (GskStroke *self,
|
||||
float limit);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
float gsk_stroke_get_miter_limit (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_dash (GskStroke *self,
|
||||
const float *dash,
|
||||
gsize n_dash);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
const float * gsk_stroke_get_dash (const GskStroke *self,
|
||||
gsize *n_dash);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_set_dash_offset (GskStroke *self,
|
||||
float offset);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
float gsk_stroke_get_dash_offset (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_stroke_to_cairo (const GskStroke *self,
|
||||
cairo_t *cr);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
58
gsk/gskstrokeprivate.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskstroke.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _GskStroke
|
||||
{
|
||||
float line_width;
|
||||
GskLineCap line_cap;
|
||||
GskLineJoin line_join;
|
||||
float miter_limit;
|
||||
|
||||
float *dash;
|
||||
gsize n_dash;
|
||||
float dash_length; /* sum of all dashes in the array */
|
||||
float dash_offset;
|
||||
};
|
||||
|
||||
static inline void
|
||||
gsk_stroke_init_copy (GskStroke *stroke,
|
||||
const GskStroke *other)
|
||||
{
|
||||
*stroke = *other;
|
||||
|
||||
stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float));
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_stroke_clear (GskStroke *stroke)
|
||||
{
|
||||
g_clear_pointer (&stroke->dash, g_free);
|
||||
stroke->n_dash = 0; /* better safe than sorry */
|
||||
}
|
||||
|
||||
float gsk_stroke_get_join_width (const GskStroke *stroke);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -25,6 +25,10 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include <gsk/gskenums.h>
|
||||
|
||||
typedef struct _GskPath GskPath;
|
||||
typedef struct _GskPathBuilder GskPathBuilder;
|
||||
typedef struct _GskPathPoint GskPathPoint;
|
||||
typedef struct _GskRenderer GskRenderer;
|
||||
typedef struct _GskStroke GskStroke;
|
||||
typedef struct _GskTransform GskTransform;
|
||||
|
||||
|
||||
@@ -23,23 +23,30 @@ gsk_private_gl_shaders = [
|
||||
]
|
||||
|
||||
gsk_public_sources = files([
|
||||
'gskdiff.c',
|
||||
'gskcairorenderer.c',
|
||||
'gskdiff.c',
|
||||
'gskglshader.c',
|
||||
'gskpath.c',
|
||||
'gskpathbuilder.c',
|
||||
'gskpathpoint.c',
|
||||
'gskrenderer.c',
|
||||
'gskrendernode.c',
|
||||
'gskrendernodeimpl.c',
|
||||
'gskrendernodeparser.c',
|
||||
'gskroundedrect.c',
|
||||
'gskstroke.c',
|
||||
'gsktransform.c',
|
||||
'gl/gskglrenderer.c',
|
||||
])
|
||||
|
||||
gsk_private_sources = files([
|
||||
'gskcairoblur.c',
|
||||
'gskcontour.c',
|
||||
'gskcurve.c',
|
||||
'gskdebug.c',
|
||||
'gskprivate.c',
|
||||
'gskprofiler.c',
|
||||
'gskspline.c',
|
||||
'gl/gskglattachmentstate.c',
|
||||
'gl/gskglbuffer.c',
|
||||
'gl/gskglcommandqueue.c',
|
||||
@@ -66,9 +73,13 @@ gsk_public_headers = files([
|
||||
'gskcairorenderer.h',
|
||||
'gskenums.h',
|
||||
'gskglshader.h',
|
||||
'gskpath.h',
|
||||
'gskpathbuilder.h',
|
||||
'gskpathpoint.h',
|
||||
'gskrenderer.h',
|
||||
'gskrendernode.h',
|
||||
'gskroundedrect.h',
|
||||
'gskstroke.h',
|
||||
'gsktransform.h',
|
||||
'gsktypes.h',
|
||||
])
|
||||
|
||||
@@ -1260,6 +1260,8 @@ static const GskVulkanRenderPassNodeFunc nodes_vtable[] = {
|
||||
[GSK_GL_SHADER_NODE] = NULL,
|
||||
[GSK_TEXTURE_SCALE_NODE] = gsk_vulkan_render_pass_add_texture_scale_node,
|
||||
[GSK_MASK_NODE] = gsk_vulkan_render_pass_add_mask_node,
|
||||
[GSK_FILL_NODE] = NULL,
|
||||
[GSK_STROKE_NODE] = NULL,
|
||||
};
|
||||
|
||||
static void
|
||||
|
||||
@@ -411,7 +411,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
|
||||
}
|
||||
|
||||
pos = gtk_list_tile_get_position (self->item_manager, tile);
|
||||
if (tile->n_items > 1)
|
||||
if (tile->n_items > 1 && tile->area.width > 0 && tile->area.height > 0)
|
||||
{
|
||||
int xspacing, yspacing;
|
||||
|
||||
|
||||
@@ -1202,7 +1202,7 @@ my_pango_layout_get_width_for_height (PangoLayout *layout,
|
||||
else if (text_height > for_height)
|
||||
min = mid + 1;
|
||||
else
|
||||
max = mid;
|
||||
max = text_width;
|
||||
}
|
||||
|
||||
return min * PANGO_SCALE;
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
* `GtkListHeader` is used by list widgets to represent the headers they
|
||||
* display.
|
||||
*
|
||||
* The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via
|
||||
* their factory, but provide a different set of properties suitable for
|
||||
* managing the header instead of individual items.
|
||||
* `GtkListHeader` objects are managed just like [class@Gtk.ListItem]
|
||||
* objects via their factory, but provide a different set of properties suitable
|
||||
* for managing the header instead of individual items.
|
||||
*
|
||||
* Since: 4.12
|
||||
*/
|
||||
|
||||
@@ -27,13 +27,14 @@
|
||||
/**
|
||||
* GtkListItem:
|
||||
*
|
||||
* `GtkListItem` is used by list widgets to represent items in a `GListModel`.
|
||||
* `GtkListItem` is used by list widgets to represent items in a
|
||||
* [iface@Gio.ListModel].
|
||||
*
|
||||
* The `GtkListItem`s are managed by the list widget (with its factory)
|
||||
* `GtkListItem` objects are managed by the list widget (with its factory)
|
||||
* and cannot be created by applications, but they need to be populated
|
||||
* by application code. This is done by calling [method@Gtk.ListItem.set_child].
|
||||
*
|
||||
* `GtkListItem`s exist in 2 stages:
|
||||
* `GtkListItem` objects exist in 2 stages:
|
||||
*
|
||||
* 1. The unbound stage where the listitem is not currently connected to
|
||||
* an item in the list. In that case, the [property@Gtk.ListItem:item]
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
/**
|
||||
* GtkOverlayLayout:
|
||||
*
|
||||
* `GtkOverlayLayout` is the layout manager used by `GtkOverlay`.
|
||||
* `GtkOverlayLayout` is the layout manager used by [class@Gtk.Overlay].
|
||||
*
|
||||
* It places widgets as overlays on top of the main child.
|
||||
*
|
||||
* This is not a reusable layout manager, since it expects its widget
|
||||
* to be a `GtkOverlay`. It only listed here so that its layout
|
||||
* to be a `GtkOverlay`. It is only listed here so that its layout
|
||||
* properties get documented.
|
||||
*/
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "gsk/gskrendernodeprivate.h"
|
||||
#include "gsk/gskroundedrectprivate.h"
|
||||
#include "gsk/gskstrokeprivate.h"
|
||||
|
||||
#include "gtk/gskpangoprivate.h"
|
||||
|
||||
@@ -105,6 +106,14 @@ struct _GtkSnapshotState {
|
||||
struct {
|
||||
GskRoundedRect bounds;
|
||||
} rounded_clip;
|
||||
struct {
|
||||
GskPath *path;
|
||||
GskFillRule fill_rule;
|
||||
} fill;
|
||||
struct {
|
||||
GskPath *path;
|
||||
GskStroke stroke;
|
||||
} stroke;
|
||||
struct {
|
||||
gsize n_shadows;
|
||||
GskShadow *shadows;
|
||||
@@ -1096,6 +1105,143 @@ gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
|
||||
gsk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_fill (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GskRenderNode *node, *fill_node;
|
||||
|
||||
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
fill_node = gsk_fill_node_new (node,
|
||||
state->data.fill.path,
|
||||
state->data.fill.fill_rule);
|
||||
|
||||
if (fill_node->bounds.size.width == 0 ||
|
||||
fill_node->bounds.size.height == 0)
|
||||
{
|
||||
gsk_render_node_unref (node);
|
||||
gsk_render_node_unref (fill_node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return fill_node;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_clear_fill (GtkSnapshotState *state)
|
||||
{
|
||||
gsk_path_unref (state->data.fill.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_push_fill:
|
||||
* @snapshot: a `GtkSnapshot`
|
||||
* @path: The path describing the area to fill
|
||||
* @fill_rule: The fill rule to use
|
||||
*
|
||||
* Fills the area given by @path and @fill_rule with an image and discards everything
|
||||
* outside of it.
|
||||
*
|
||||
* The image is recorded until the next call to [method@Gtk.Snapshot.pop].
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_push_fill (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
GtkSnapshotState *state;
|
||||
|
||||
gtk_snapshot_ensure_identity (snapshot);
|
||||
|
||||
state = gtk_snapshot_push_state (snapshot,
|
||||
gtk_snapshot_get_current_state (snapshot)->transform,
|
||||
gtk_snapshot_collect_fill,
|
||||
gtk_snapshot_clear_fill);
|
||||
|
||||
state->data.fill.path = gsk_path_ref (path);
|
||||
state->data.fill.fill_rule = fill_rule;
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_stroke (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GskRenderNode *node, *stroke_node;
|
||||
|
||||
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
stroke_node = gsk_stroke_node_new (node,
|
||||
state->data.stroke.path,
|
||||
&state->data.stroke.stroke);
|
||||
|
||||
if (stroke_node->bounds.size.width == 0 ||
|
||||
stroke_node->bounds.size.height == 0)
|
||||
{
|
||||
gsk_render_node_unref (node);
|
||||
gsk_render_node_unref (stroke_node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return stroke_node;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_clear_stroke (GtkSnapshotState *state)
|
||||
{
|
||||
gsk_path_unref (state->data.stroke.path);
|
||||
gsk_stroke_clear (&state->data.stroke.stroke);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_push_stroke:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @path: The path to stroke
|
||||
* @stroke: The stroke attributes
|
||||
*
|
||||
* Strokes the given @path with the attributes given by @stroke and
|
||||
* an image.
|
||||
*
|
||||
* The image is recorded until the next call to [method@Gtk.Snapshot.pop].
|
||||
*
|
||||
* Note that the strokes are subject to the same transformation as
|
||||
* everything else, so uneven scaling will cause horizontal and vertical
|
||||
* strokes to have different widths.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke)
|
||||
{
|
||||
GtkSnapshotState *state;
|
||||
|
||||
gtk_snapshot_ensure_identity (snapshot);
|
||||
|
||||
state = gtk_snapshot_push_state (snapshot,
|
||||
gtk_snapshot_get_current_state (snapshot)->transform,
|
||||
gtk_snapshot_collect_stroke,
|
||||
gtk_snapshot_clear_stroke);
|
||||
|
||||
state->data.stroke.path = gsk_path_ref (path);
|
||||
gsk_stroke_init_copy (&state->data.stroke.stroke, stroke);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
|
||||
@@ -87,6 +87,14 @@ void gtk_snapshot_push_clip (GtkSnapshot
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
|
||||
const GskRoundedRect *bounds);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gtk_snapshot_push_fill (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
|
||||
const GskShadow *shadow,
|
||||
|
||||
@@ -298,6 +298,12 @@ create_list_model_for_render_node (GskRenderNode *node)
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_rounded_clip_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_fill_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_stroke_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, 1);
|
||||
|
||||
@@ -425,6 +431,10 @@ node_type_name (GskRenderNodeType type)
|
||||
return "Clip";
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
return "Rounded Clip";
|
||||
case GSK_FILL_NODE:
|
||||
return "Fill";
|
||||
case GSK_STROKE_NODE:
|
||||
return "Stroke";
|
||||
case GSK_SHADOW_NODE:
|
||||
return "Shadow";
|
||||
case GSK_BLEND_NODE:
|
||||
@@ -466,6 +476,8 @@ node_name (GskRenderNode *node)
|
||||
case GSK_REPEAT_NODE:
|
||||
case GSK_CLIP_NODE:
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
case GSK_SHADOW_NODE:
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
@@ -894,6 +906,20 @@ add_float_row (GListStore *store,
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
static const char *
|
||||
enum_to_nick (GType type,
|
||||
int value)
|
||||
{
|
||||
GEnumClass *class;
|
||||
GEnumValue *v;
|
||||
|
||||
class = g_type_class_ref (type);
|
||||
v = g_enum_get_value (class, value);
|
||||
g_type_class_unref (class);
|
||||
|
||||
return v->value_nick;
|
||||
}
|
||||
|
||||
static void
|
||||
populate_render_node_properties (GListStore *store,
|
||||
GskRenderNode *node)
|
||||
@@ -1133,9 +1159,7 @@ populate_render_node_properties (GListStore *store,
|
||||
case GSK_BLEND_NODE:
|
||||
{
|
||||
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
|
||||
tmp = g_enum_to_string (GSK_TYPE_BLEND_MODE, mode);
|
||||
add_text_row (store, "Blendmode", tmp);
|
||||
g_free (tmp);
|
||||
add_text_row (store, "Blendmode", enum_to_nick (GSK_TYPE_BLEND_MODE, mode));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1377,6 +1401,39 @@ populate_render_node_properties (GListStore *store,
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
{
|
||||
GskPath *path = gsk_fill_node_get_path (node);
|
||||
GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node);
|
||||
|
||||
tmp = gsk_path_to_string (path);
|
||||
add_text_row (store, "Path", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
add_text_row (store, "Fill rule", enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
{
|
||||
GskPath *path = gsk_stroke_node_get_path (node);
|
||||
const GskStroke *stroke = gsk_stroke_node_get_stroke (node);
|
||||
GskLineCap line_cap = gsk_stroke_get_line_cap (stroke);
|
||||
GskLineJoin line_join = gsk_stroke_get_line_join (stroke);
|
||||
|
||||
tmp = gsk_path_to_string (path);
|
||||
add_text_row (store, "Path", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
tmp = g_strdup_printf ("%.2f", gsk_stroke_get_line_width (stroke));
|
||||
add_text_row (store, "Line width", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
add_text_row (store, "Line cap", enum_to_nick (GSK_TYPE_LINE_CAP, line_cap));
|
||||
add_text_row (store, "Line join", enum_to_nick (GSK_TYPE_LINE_JOIN, line_join));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_CONTAINER_NODE:
|
||||
tmp = g_strdup_printf ("%d", gsk_container_node_get_n_children (node));
|
||||
add_text_row (store, "Children", tmp);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#endif
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@@ -87,28 +88,39 @@ struct _GtkPrinterOptionClass
|
||||
void (*_gtk_reserved4) (void);
|
||||
};
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gtk_printer_option_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GtkPrinterOption *gtk_printer_option_new (const char *name,
|
||||
const char *display_text,
|
||||
GtkPrinterOptionType type);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_set (GtkPrinterOption *option,
|
||||
const char *value);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_set_has_conflict (GtkPrinterOption *option,
|
||||
gboolean has_conflict);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_clear_has_conflict (GtkPrinterOption *option);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_set_boolean (GtkPrinterOption *option,
|
||||
gboolean value);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_allocate_choices (GtkPrinterOption *option,
|
||||
int num);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_choices_from_array (GtkPrinterOption *option,
|
||||
int num_choices,
|
||||
const char **choices,
|
||||
const char **choices_display);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_printer_option_has_choice (GtkPrinterOption *option,
|
||||
const char *choice);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_printer_option_set_activates_default (GtkPrinterOption *option,
|
||||
gboolean activates);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gtk_printer_option_get_activates_default (GtkPrinterOption *option);
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include "gtkprintbackendprivate.h"
|
||||
#include "gtk/print/gtkprintbackendprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <cpdb/cpdb-frontend.h>
|
||||
#include <gtk/gtkprinterprivate.h>
|
||||
#include <gtk/print/gtkprinterprivate.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@@ -43,4 +43,4 @@ void gtk_printer_cpdb_set_printer_obj (GtkPrinterCpdb
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GTK_PRINTER_CPDB_H__ */
|
||||
#endif /* __GTK_PRINTER_CPDB_H__ */
|
||||
|
||||
92
testsuite/gsk/boundingbox.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright © 2023 Red Hat, 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.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gsk/gskboundingboxprivate.h>
|
||||
|
||||
static void
|
||||
init_random_rect (graphene_rect_t *r)
|
||||
{
|
||||
r->origin.x = g_test_rand_double_range (0, 1000);
|
||||
r->origin.y = g_test_rand_double_range (0, 1000);
|
||||
r->size.width = g_test_rand_double_range (0, 1000);
|
||||
r->size.height = g_test_rand_double_range (0, 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
test_to_rect (void)
|
||||
{
|
||||
graphene_rect_t rect, rect2;
|
||||
GskBoundingBox bb;
|
||||
graphene_point_t p, p2;
|
||||
|
||||
for (unsigned int i = 0; i < 100; i++)
|
||||
{
|
||||
init_random_rect (&rect);
|
||||
|
||||
gsk_bounding_box_init_from_rect (&bb, &rect);
|
||||
gsk_bounding_box_to_rect (&bb, &rect2);
|
||||
|
||||
graphene_rect_get_top_left (&rect, &p);
|
||||
graphene_rect_get_top_left (&rect2, &p2);
|
||||
|
||||
/* Note: that we can't assert equality here is the reason
|
||||
* GskBoundingBox exists.
|
||||
*/
|
||||
g_assert_true (graphene_point_near (&p, &p2, 0.001));
|
||||
|
||||
graphene_rect_get_bottom_right (&rect, &p);
|
||||
graphene_rect_get_bottom_right (&rect2, &p2);
|
||||
|
||||
g_assert_true (graphene_point_near (&p, &p2, 0.001));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_contains (void)
|
||||
{
|
||||
graphene_rect_t rect;
|
||||
GskBoundingBox bb;
|
||||
graphene_point_t p;
|
||||
|
||||
for (unsigned int i = 0; i < 100; i++)
|
||||
{
|
||||
init_random_rect (&rect);
|
||||
|
||||
gsk_bounding_box_init_from_rect (&bb, &rect);
|
||||
|
||||
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.min));
|
||||
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.max));
|
||||
|
||||
graphene_point_interpolate (&bb.min, &bb.max, 0.5, &p);
|
||||
g_assert_true (gsk_bounding_box_contains_point (&bb, &p));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/bounding-box/to-rect", test_to_rect);
|
||||
g_test_add_func ("/bounding-box/contains", test_contains);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
13
testsuite/gsk/compare/fill.node
Normal file
@@ -0,0 +1,13 @@
|
||||
fill {
|
||||
child: color {
|
||||
bounds: 0 0 100 100;
|
||||
color: rgb(255,0,0);
|
||||
}
|
||||
path: "\
|
||||
M 10 10\
|
||||
L 90 10\
|
||||
L 90 90\
|
||||
L 10 90\
|
||||
Z";
|
||||
fill-rule: winding;
|
||||
}
|
||||
BIN
testsuite/gsk/compare/fill.png
Normal file
|
After Width: | Height: | Size: 250 B |
19
testsuite/gsk/compare/stroke.node
Normal file
@@ -0,0 +1,19 @@
|
||||
color {
|
||||
bounds: 0 0 100 100;
|
||||
color: rgb(0,0,0);
|
||||
}
|
||||
stroke {
|
||||
child: color {
|
||||
bounds: 0 0 100 100;
|
||||
color: rgb(255,0,0);
|
||||
}
|
||||
path: "\
|
||||
M 10 10\
|
||||
L 90 10\
|
||||
L 90 90\
|
||||
L 10 90\
|
||||
Z";
|
||||
line-width: 2;
|
||||
line-cap: butt;
|
||||
line-join: miter;
|
||||
}
|
||||
BIN
testsuite/gsk/compare/stroke.png
Normal file
|
After Width: | Height: | Size: 346 B |
159
testsuite/gsk/curve-special-cases.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 100, 50);
|
||||
graphene_point_init (&p[3], 100, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_degenerate_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 0);
|
||||
graphene_point_init (&p[2], 100, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 50, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pathop_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskCurve *curve = user_data;
|
||||
|
||||
g_assert (op != GSK_PATH_CLOSE);
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
return TRUE;
|
||||
|
||||
gsk_curve_init_foreach (curve, op, pts, n_pts);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
parse_curve (GskCurve *c,
|
||||
const char *str)
|
||||
{
|
||||
GskPath *path = gsk_path_parse (str);
|
||||
|
||||
gsk_path_foreach (path, -1, pathop_cb, c);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_crossing (void)
|
||||
{
|
||||
struct {
|
||||
const char *c;
|
||||
const graphene_point_t p;
|
||||
int crossing;
|
||||
} tests[] = {
|
||||
{ "M 0 0 L 200 200", { 200, 100 }, 0 },
|
||||
{ "M 0 0 L 200 200", { 0, 100 }, 1 },
|
||||
{ "M 0 200 L 200 0", { 0, 100 }, -1 },
|
||||
{ "M 0 0 C 100 100 200 200 300 300", { 200, 100 }, 0 },
|
||||
{ "M 0 0 C 100 100 200 200 300 300", { 0, 100 }, 1 },
|
||||
{ "M 0 300 C 100 200 200 100 300 0", { 0, 100 }, -1 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 0, 150 }, 1 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 100, 150 }, 0 },
|
||||
{ "M 0 0 C 100 600 200 -300 300 300", { 200, 150 }, 1 },
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
parse_curve (&c, tests[i].c);
|
||||
|
||||
g_assert_true (gsk_curve_get_crossing (&c, &tests[i].p) == tests[i].crossing);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
|
||||
g_test_add_func ("/curve/special/crossing", test_curve_crossing);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
337
testsuite/gsk/curve.c
Normal file
@@ -0,0 +1,337 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
init_random_point (graphene_point_t *p)
|
||||
{
|
||||
p->x = g_test_rand_double_range (0, 1000);
|
||||
p->y = g_test_rand_double_range (0, 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve_with_op (GskCurve *curve,
|
||||
GskPathOperation min_op,
|
||||
GskPathOperation max_op)
|
||||
{
|
||||
switch (g_test_rand_int_range (min_op, max_op + 1))
|
||||
{
|
||||
case GSK_PATH_LINE:
|
||||
{
|
||||
graphene_point_t p[2];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
graphene_point_t p[3];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
init_random_point (&p[3]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve (GskCurve *curve)
|
||||
{
|
||||
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CUBIC);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_vec2_t vec, exact;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_get_tangent (&c, 0, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_start_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
|
||||
gsk_curve_get_tangent (&c, 1, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_end_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_points (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
/* We can assert equality here because evaluating the polynomials with 0
|
||||
* has no effect on accuracy.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 0, &p);
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
|
||||
/* But here we evaluate the polynomials with 1 which gives the highest possible
|
||||
* accuracy error. So we'll just be generous here.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 1, &p);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
/* at this point the subdivision stops and the decomposer
|
||||
* violates tolerance rules
|
||||
*/
|
||||
#define MIN_PROGRESS (1/1024.f)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
graphene_point_t p;
|
||||
float t;
|
||||
} PointOnLine;
|
||||
|
||||
static gboolean
|
||||
add_line_to_array (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
|
||||
|
||||
g_assert_true (array->len > 0);
|
||||
g_assert_cmpfloat (from_progress, >=, 0.0f);
|
||||
g_assert_cmpfloat (from_progress, <, to_progress);
|
||||
g_assert_cmpfloat (to_progress, <=, 1.0f);
|
||||
|
||||
g_assert_true (graphene_point_equal (&last->p, from));
|
||||
g_assert_cmpfloat (last->t, ==, from_progress);
|
||||
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GArray *array;
|
||||
GskCurve c;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
|
||||
|
||||
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
|
||||
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
|
||||
graphene_point_t p;
|
||||
|
||||
/* Check that the points we got are actually on the line */
|
||||
gsk_curve_get_point (&c, pol->t, &p);
|
||||
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
|
||||
|
||||
/* Check that the mid point is not further than the tolerance */
|
||||
if (j > 0)
|
||||
{
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
|
||||
graphene_point_t mid;
|
||||
|
||||
if (pol->t - last->t > MIN_PROGRESS)
|
||||
{
|
||||
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
|
||||
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
|
||||
/* The decomposer does this cheaper Manhattan distance test,
|
||||
* so graphene_point_near() does not work */
|
||||
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
|
||||
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_curve_to_array (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init_foreach (&c, op, pts, n_pts);
|
||||
g_array_append_val (array, c);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into (GskPathForeachFlags flags)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskPathBuilder *builder;
|
||||
const graphene_point_t *s;
|
||||
GskPath *path;
|
||||
GArray *array;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
s = gsk_curve_get_start_point (&c);
|
||||
gsk_path_builder_move_to (builder, s->x, s->y);
|
||||
gsk_curve_builder_to (&c, builder);
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
|
||||
|
||||
g_assert_true (gsk_curve_decompose_curve (&c, flags, 0.1, add_curve_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 1);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
GskCurve *c2 = &g_array_index (array, GskCurve, j);
|
||||
|
||||
switch (c2->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
break;
|
||||
case GSK_PATH_QUAD:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
break;
|
||||
case GSK_PATH_CUBIC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_line (void)
|
||||
{
|
||||
test_curve_decompose_into (0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_quad (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_cubic (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
}
|
||||
|
||||
/* Some sanity checks for splitting curves. */
|
||||
static void
|
||||
test_curve_split (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p;
|
||||
graphene_vec2_t t, t1, t2;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_split (&c, 0.5, &c1, &c2);
|
||||
|
||||
g_assert_true (c1.op == c.op);
|
||||
g_assert_true (c2.op == c.op);
|
||||
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c),
|
||||
gsk_curve_get_start_point (&c1), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1),
|
||||
gsk_curve_get_start_point (&c2), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c),
|
||||
gsk_curve_get_end_point (&c2), 0.005));
|
||||
gsk_curve_get_point (&c, 0.5, &p);
|
||||
gsk_curve_get_tangent (&c, 0.5, &t);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1), &p, 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c2), &p, 0.005));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t1);
|
||||
gsk_curve_get_start_tangent (&c1, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c1, &t1);
|
||||
gsk_curve_get_start_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t1, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c, &t1);
|
||||
gsk_curve_get_end_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/points", test_curve_points);
|
||||
g_test_add_func ("/curve/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/decompose", test_curve_decompose);
|
||||
g_test_add_func ("/curve/decompose/into/line", test_curve_decompose_into_line);
|
||||
g_test_add_func ("/curve/decompose/into/quad", test_curve_decompose_into_quad);
|
||||
g_test_add_func ("/curve/decompose/into/cubic", test_curve_decompose_into_cubic);
|
||||
g_test_add_func ("/curve/split", test_curve_split);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -59,6 +59,7 @@ compare_render_tests = [
|
||||
'empty-shadow',
|
||||
'empty-texture',
|
||||
'empty-transform',
|
||||
'fill',
|
||||
'huge-height',
|
||||
'huge-width',
|
||||
'inset-shadow-multiple',
|
||||
@@ -88,6 +89,7 @@ compare_render_tests = [
|
||||
'scaled-cairo',
|
||||
'scaled-texture',
|
||||
'shadow-in-opacity',
|
||||
'stroke',
|
||||
'texture-scale-magnify-10000x',
|
||||
'texture-scale-magnify-rotate',
|
||||
'texture-scale-stripes',
|
||||
@@ -270,6 +272,8 @@ node_parser_tests = [
|
||||
'empty-cross-fade.ref.node',
|
||||
'empty-debug.node',
|
||||
'empty-debug.ref.node',
|
||||
'empty-fill.node',
|
||||
'empty-fill.ref.node',
|
||||
'empty-inset-shadow.node',
|
||||
'empty-inset-shadow.ref.node',
|
||||
'empty-linear-gradient.node',
|
||||
@@ -286,6 +290,8 @@ node_parser_tests = [
|
||||
'empty-rounded-clip.ref.node',
|
||||
'empty-shadow.node',
|
||||
'empty-shadow.ref.node',
|
||||
'empty-stroke.node',
|
||||
'empty-stroke.ref.node',
|
||||
'empty-text.node',
|
||||
'empty-text.ref.node',
|
||||
'empty-texture.node',
|
||||
@@ -294,6 +300,8 @@ node_parser_tests = [
|
||||
'empty-texture-scale.ref.node',
|
||||
'empty-transform.node',
|
||||
'empty-transform.ref.node',
|
||||
'fill.node',
|
||||
'fill.ref.node',
|
||||
'glshader.node',
|
||||
'glshader.ref.node',
|
||||
'glshader.errors',
|
||||
@@ -320,6 +328,8 @@ node_parser_tests = [
|
||||
'string-error.errors',
|
||||
'string-error.node',
|
||||
'string-error.ref.node',
|
||||
'stroke.node',
|
||||
'stroke.ref.node',
|
||||
'testswitch.node',
|
||||
'text-fail.node',
|
||||
'text-fail.ref.node',
|
||||
@@ -359,6 +369,8 @@ endforeach
|
||||
tests = [
|
||||
['transform'],
|
||||
['shader'],
|
||||
['path'],
|
||||
['path-special-cases'],
|
||||
]
|
||||
|
||||
test_cargs = []
|
||||
@@ -389,10 +401,13 @@ foreach t : tests
|
||||
endforeach
|
||||
|
||||
internal_tests = [
|
||||
[ 'curve' ],
|
||||
[ 'curve-special-cases' ],
|
||||
[ 'diff' ],
|
||||
[ 'half-float' ],
|
||||
['rounded-rect'],
|
||||
['misc'],
|
||||
['boundingbox'],
|
||||
]
|
||||
|
||||
foreach t : internal_tests
|
||||
|
||||
1
testsuite/gsk/nodeparser/empty-fill.node
Normal file
@@ -0,0 +1 @@
|
||||
fill { }
|
||||
14
testsuite/gsk/nodeparser/empty-fill.ref.node
Normal file
@@ -0,0 +1,14 @@
|
||||
fill {
|
||||
child: color {
|
||||
bounds: 0 0 50 50;
|
||||
color: rgb(255,0,204);
|
||||
}
|
||||
path: "\
|
||||
M 25 0\
|
||||
L 39.694629669189453 45.225425720214844\
|
||||
L 1.2235870361328125 17.274574279785156\
|
||||
L 48.776412963867188 17.274574279785156\
|
||||
L 10.305368423461914 45.225425720214844\
|
||||
Z";
|
||||
fill-rule: winding;
|
||||
}
|
||||
1
testsuite/gsk/nodeparser/empty-stroke.node
Normal file
@@ -0,0 +1 @@
|
||||
stroke { }
|
||||
16
testsuite/gsk/nodeparser/empty-stroke.ref.node
Normal file
@@ -0,0 +1,16 @@
|
||||
stroke {
|
||||
child: color {
|
||||
bounds: 0 0 50 50;
|
||||
color: rgb(255,0,204);
|
||||
}
|
||||
path: "\
|
||||
M 25 0\
|
||||
L 39.694629669189453 45.225425720214844\
|
||||
L 1.2235870361328125 17.274574279785156\
|
||||
L 48.776412963867188 17.274574279785156\
|
||||
L 10.305368423461914 45.225425720214844\
|
||||
Z";
|
||||
line-width: 1;
|
||||
line-cap: butt;
|
||||
line-join: miter;
|
||||
}
|
||||
4
testsuite/gsk/nodeparser/fill.node
Normal file
@@ -0,0 +1,4 @@
|
||||
fill {
|
||||
path: "M0,0L50,0C50,50 0,50 0,0Z";
|
||||
fill-rule: winding;
|
||||
}
|
||||
12
testsuite/gsk/nodeparser/fill.ref.node
Normal file
@@ -0,0 +1,12 @@
|
||||
fill {
|
||||
child: color {
|
||||
bounds: 0 0 50 50;
|
||||
color: rgb(255,0,204);
|
||||
}
|
||||
path: "\
|
||||
M 0 0\
|
||||
L 50 0\
|
||||
C 50 50, 0 50, 0 0\
|
||||
Z";
|
||||
fill-rule: winding;
|
||||
}
|
||||
6
testsuite/gsk/nodeparser/stroke.node
Normal file
@@ -0,0 +1,6 @@
|
||||
stroke {
|
||||
child: color { bounds: 0 0 100 100; color: red; }
|
||||
path: "M10,10L90,90M90,10L10,90";
|
||||
line-width: 10;
|
||||
line-cap: round;
|
||||
}
|
||||
14
testsuite/gsk/nodeparser/stroke.ref.node
Normal file
@@ -0,0 +1,14 @@
|
||||
stroke {
|
||||
child: color {
|
||||
bounds: 0 0 100 100;
|
||||
color: rgb(255,0,0);
|
||||
}
|
||||
path: "\
|
||||
M 10 10\
|
||||
L 90 90\
|
||||
M 90 10\
|
||||
L 10 90";
|
||||
line-width: 10;
|
||||
line-cap: round;
|
||||
line-join: miter;
|
||||
}
|
||||
708
testsuite/gsk/path-special-cases.c
Normal file
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
/* testcases from path_parser.rs in librsvg */
|
||||
static void
|
||||
test_rsvg_parse (void)
|
||||
{
|
||||
struct {
|
||||
const char *in;
|
||||
const char *out;
|
||||
} tests[] = {
|
||||
{ "", "" },
|
||||
// numbers
|
||||
{ "M 10 20", "M 10 20" },
|
||||
{ "M -10 -20", "M -10 -20" },
|
||||
{ "M .10 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10 -0.20", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M10.5.50", "M 10.5 0.5" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1 .20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E1-.20", "M -1 -0.2" },
|
||||
{ "M10.10E2 -0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2-0.20e-3", "M -1010 -0.0002" },
|
||||
{ "M1e2.5", "M 100 0.5" },
|
||||
{ "M1e-2.5", "M 0.01 0.5" },
|
||||
{ "M1e+2.5", "M 100 0.5" },
|
||||
// bogus numbers
|
||||
{ "M+", NULL },
|
||||
{ "M-", NULL },
|
||||
{ "M+x", NULL },
|
||||
{ "M10e", NULL },
|
||||
{ "M10ex", NULL },
|
||||
{ "M10e-", NULL },
|
||||
{ "M10e+x", NULL },
|
||||
// numbers with comma
|
||||
{ "M 10, 20", "M 10 20" },
|
||||
{ "M -10,-20", "M -10 -20" },
|
||||
{ "M.10 , 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10, -0.20 ", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1,.20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E-2,-.20", "M -0.001 -0.2" },
|
||||
{ "M10.10E2,-0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2,-0.20e-3", "M -1010 -0.0002" },
|
||||
// single moveto
|
||||
{ "M 10 20 ", "M 10 20" },
|
||||
{ "M10,20 ", "M 10 20" },
|
||||
{ "M10 20 ", "M 10 20" },
|
||||
{ " M10,20 ", "M 10 20" },
|
||||
// relative moveto
|
||||
{ "m10 20", "M 10 20" },
|
||||
// absolute moveto with implicit lineto
|
||||
{ "M10 20 30 40", "M 10 20 L 30 40" },
|
||||
{ "M10,20,30,40", "M 10 20 L 30 40" },
|
||||
{ "M.1-2,3E2-4", "M 0.1 -2 L 300 -4" },
|
||||
// relative moveto with implicit lineto
|
||||
{ "m10 20 30 40", "M 10 20 L 40 60" },
|
||||
// relative moveto with relative lineto sequence
|
||||
{ "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12",
|
||||
"M 46 447 L 46 447.5 L 45 447.5 L 44 447.5 L 44 448.5 L 44 460.5" },
|
||||
// absolute moveto with implicit linetos
|
||||
{ "M10,20 30,40,50 60", "M 10 20 L 30 40 L 50 60" },
|
||||
// relative moveto with implicit linetos
|
||||
{ "m10 20 30 40 50 60", "M 10 20 L 40 60 L 90 120" },
|
||||
// absolute moveto moveto
|
||||
{ "M10 20 M 30 40", "M 10 20 M 30 40" },
|
||||
// relative moveto moveto
|
||||
{ "m10 20 m 30 40", "M 10 20 M 40 60" },
|
||||
// relative moveto lineto moveto
|
||||
{ "m10 20 30 40 m 50 60", "M 10 20 L 40 60 M 90 120" },
|
||||
// absolute moveto lineto
|
||||
{ "M10 20 L30,40", "M 10 20 L 30 40" },
|
||||
// relative moveto lineto
|
||||
{ "m10 20 l30,40", "M 10 20 L 40 60" },
|
||||
// relative moveto lineto lineto abs lineto
|
||||
{ "m10 20 30 40l30,40,50 60L200,300",
|
||||
"M 10 20 L 40 60 L 70 100 L 120 160 L 200 300" },
|
||||
// horizontal lineto
|
||||
{ "M10 20 H30", "M 10 20 L 30 20" },
|
||||
{ "M 10 20 H 30 40", "M 10 20 L 30 20 L 40 20" },
|
||||
{ "M10 20 H30,40-50", "M 10 20 L 30 20 L 40 20 L -50 20" },
|
||||
{ "m10 20 h30,40-50", "M 10 20 L 40 20 L 80 20 L 30 20" },
|
||||
// vertical lineto
|
||||
{ "M10 20 V30", "M 10 20 L 10 30" },
|
||||
{ "M10 20 V30 40", "M 10 20 L 10 30 L 10 40" },
|
||||
{ "M10 20 V30,40-50", "M 10 20 L 10 30 L 10 40 L 10 -50" },
|
||||
{ "m10 20 v30,40-50", "M 10 20 L 10 50 L 10 90 L 10 40" },
|
||||
// curveto
|
||||
{ "M10 20 C 30,40 50 60-70,80", "M 10 20 C 30 40, 50 60, -70 80" },
|
||||
{ "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 30 40, 50 60, -70 80 C 90 100, 110 120, 130 140" },
|
||||
{ "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
{ "m10 20 c 30,40 50 60-70,80 90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
// smooth curveto
|
||||
{ "M10 20 S 30,40-50,60", "M 10 20 C 10 20, 30 40, -50 60" },
|
||||
{ "M10 20 S 30,40 50 60-70,80,90 100",
|
||||
"M 10 20 C 10 20, 30 40, 50 60 C 70 80, -70 80, 90 100" },
|
||||
// quadratic curveto
|
||||
{ "M10 20 Q30 40 50 60", "M 10 20 Q 30 40, 50 60" },
|
||||
{ "M10 20 Q30 40 50 60,70,80-90 100",
|
||||
"M 10 20 Q 30 40, 50 60 Q 70 80, -90 100" },
|
||||
{ "m10 20 q 30,40 50 60-70,80 90 100",
|
||||
"M 10 20 Q 40 60, 60 80 Q -10 160, 150 180" },
|
||||
// smooth quadratic curveto
|
||||
{ "M10 20 T30 40", "M 10 20 Q 10 20, 30 40" },
|
||||
{ "M10 20 Q30 40 50 60 T70 80", "M 10 20 Q 30 40, 50 60 Q 70 80, 70 80" },
|
||||
{ "m10 20 q 30,40 50 60t-70,80",
|
||||
"M 10 20 Q 40 60, 60 80 Q 80 100, -10 160" },
|
||||
// elliptical arc. Exact numbers depend on too much math, so just verify
|
||||
// that these parse successfully
|
||||
{ "M 1 3 A 1 2 3 00 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 016 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 10,6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1,1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 16 7", "path" },
|
||||
// close path
|
||||
{ "M10 20 Z", "M 10 20 Z" },
|
||||
{ "m10 20 30 40 m 50 60 70 80 90 100z", "M 10 20 L 40 60 M 90 120 L 160 200 L 250 300 Z" },
|
||||
// must start with moveto
|
||||
{ " L10 20", NULL },
|
||||
// moveto args
|
||||
{ "M", NULL },
|
||||
{ "M,", NULL },
|
||||
{ "M10", NULL },
|
||||
{ "M10,", NULL },
|
||||
{ "M10x", NULL },
|
||||
{ "M10,x", NULL },
|
||||
{ "M10-20,", NULL },
|
||||
{ "M10-20-30", NULL },
|
||||
{ "M10-20-30 x", NULL },
|
||||
// closepath args
|
||||
{ "M10-20z10", NULL },
|
||||
{ "M10-20z,", NULL },
|
||||
// lineto args
|
||||
{ "M10-20L10", NULL },
|
||||
{ "M 10,10 L 20,20,30", NULL },
|
||||
{ "M 10,10 L 20,20,", NULL },
|
||||
// horizontal lineto args
|
||||
{ "M10-20H", NULL },
|
||||
{ "M10-20H,", NULL },
|
||||
{ "M10-20H30,", NULL },
|
||||
// vertical lineto args
|
||||
{ "M10-20v", NULL },
|
||||
{ "M10-20v,", NULL },
|
||||
{ "M10-20v30,", NULL },
|
||||
// curveto args
|
||||
{ "M10-20C1", NULL },
|
||||
{ "M10-20C1,", NULL },
|
||||
{ "M10-20C1 2", NULL },
|
||||
{ "M10-20C1,2,", NULL },
|
||||
{ "M10-20C1 2 3", NULL },
|
||||
{ "M10-20C1,2,3", NULL },
|
||||
{ "M10-20C1,2,3,", NULL },
|
||||
{ "M10-20C1 2 3 4", NULL },
|
||||
{ "M10-20C1,2,3,4", NULL },
|
||||
{ "M10-20C1,2,3,4,", NULL },
|
||||
{ "M10-20C1 2 3 4 5", NULL },
|
||||
{ "M10-20C1,2,3,4,5", NULL },
|
||||
{ "M10-20C1,2,3,4,5,", NULL },
|
||||
{ "M10-20C1,2,3,4,5,6,", NULL },
|
||||
// smooth curveto args
|
||||
{ "M10-20S1", NULL },
|
||||
{ "M10-20S1,", NULL },
|
||||
{ "M10-20S1 2", NULL },
|
||||
{ "M10-20S1,2,", NULL },
|
||||
{ "M10-20S1 2 3", NULL },
|
||||
{ "M10-20S1,2,3,", NULL },
|
||||
{ "M10-20S1,2,3,4,", NULL },
|
||||
// quadratic curveto args
|
||||
{ "M10-20Q1", NULL },
|
||||
{ "M10-20Q1,", NULL },
|
||||
{ "M10-20Q1 2", NULL },
|
||||
{ "M10-20Q1,2,", NULL },
|
||||
{ "M10-20Q1 2 3", NULL },
|
||||
{ "M10-20Q1,2,3", NULL },
|
||||
{ "M10-20Q1,2,3,", NULL },
|
||||
{ "M10 20 Q30 40 50 60,", NULL },
|
||||
// smooth quadratic curveto args
|
||||
{ "M10-20T1", NULL },
|
||||
{ "M10-20T1,", NULL },
|
||||
{ "M10 20 T 30 40,", NULL },
|
||||
// elliptical arc args
|
||||
{ "M10-20A1", NULL },
|
||||
{ "M10-20A1,", NULL },
|
||||
{ "M10-20A1 2", NULL },
|
||||
{ "M10-20A1 2,", NULL },
|
||||
{ "M10-20A1 2 3", NULL },
|
||||
{ "M10-20A1 2 3,", NULL },
|
||||
{ "M10-20A1 2 3 4", NULL },
|
||||
{ "M10-20A1 2 3 1", NULL },
|
||||
{ "M10-20A1 2 3,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 5", NULL },
|
||||
{ "M10-20A1 2 3 1 1", NULL },
|
||||
{ "M10-20A1 2 3,1,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 1 6", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,", NULL },
|
||||
{ "M 1 2 A 1 2 3 1.0 0.0 6 7", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,7,", NULL },
|
||||
// misc
|
||||
{ "M.. 1,0 0,100000", NULL },
|
||||
{ "M 10 20,M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, ", NULL },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskPath *path;
|
||||
char *string;
|
||||
char *string2;
|
||||
|
||||
if (g_test_verbose ())
|
||||
g_print ("%d: %s\n", i, tests[i].in);
|
||||
|
||||
path = gsk_path_parse (tests[i].in);
|
||||
if (tests[i].out)
|
||||
{
|
||||
g_assert_nonnull (path);
|
||||
string = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
if (strcmp (tests[i].out, "path") != 0)
|
||||
{
|
||||
/* Preferred, but doesn't work, because gsk_path_print()
|
||||
* prints numbers with insane accuracy
|
||||
*/
|
||||
/* g_assert_cmpstr (tests[i].out, ==, string); */
|
||||
path = gsk_path_parse (tests[i].out);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string2);
|
||||
}
|
||||
|
||||
path = gsk_path_parse (string);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string);
|
||||
g_free (string2);
|
||||
}
|
||||
else
|
||||
g_assert_null (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_empty_path (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
char *s;
|
||||
graphene_rect_t bounds;
|
||||
GskPathPoint point;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
g_assert_true (gsk_path_is_empty (path));
|
||||
g_assert_false (gsk_path_is_closed (path));
|
||||
|
||||
s = gsk_path_to_string (path);
|
||||
g_assert_cmpstr (s, ==, "");
|
||||
g_free (s);
|
||||
|
||||
g_assert_false (gsk_path_get_bounds (path, &bounds));
|
||||
g_assert_true (graphene_rect_equal (&bounds, graphene_rect_zero ()));
|
||||
|
||||
g_assert_false (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
|
||||
|
||||
g_assert_false (gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (0, 0), INFINITY, &point));
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rect_path (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
char *s;
|
||||
graphene_rect_t bounds;
|
||||
GskPathPoint point;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 200, 100));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
g_assert_false (gsk_path_is_empty (path));
|
||||
g_assert_true (gsk_path_is_closed (path));
|
||||
|
||||
s = gsk_path_to_string (path);
|
||||
g_assert_cmpstr (s, ==, "M 0 0 L 200 0 L 200 100 L 0 100 Z");
|
||||
g_free (s);
|
||||
|
||||
g_assert_true (gsk_path_get_bounds (path, &bounds));
|
||||
g_assert_true (graphene_rect_equal (&bounds, &GRAPHENE_RECT_INIT (0, 0, 200, 100)));
|
||||
|
||||
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
|
||||
g_assert_false (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (200, 200), GSK_FILL_RULE_WINDING));
|
||||
|
||||
g_assert_true (gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (200, 200), INFINITY, &point));
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
/* test quad <> cubic conversions */
|
||||
static gboolean
|
||||
collect_path (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder *builder = user_data;
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
return TRUE;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y,
|
||||
pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_foreach (void)
|
||||
{
|
||||
const char *s = "M 0 0 Q 9 0, 9 9 Q 99 9, 99 18 Z";
|
||||
const char *sp = "M 0 0 C 6 0, 9 3, 9 9 C 69 9, 99 12, 99 18 Z";
|
||||
GskPath *path, *path2;
|
||||
char *s2;
|
||||
GskPathBuilder *builder;
|
||||
|
||||
path = gsk_path_parse (s);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_QUAD, collect_path, builder);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
s2 = gsk_path_to_string (path2);
|
||||
|
||||
g_assert_cmpstr (s, ==, s2);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
g_free (s2);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC, collect_path, builder);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
s2 = gsk_path_to_string (path2);
|
||||
|
||||
g_assert_cmpstr (s, ==, s2);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
g_free (s2);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CUBIC, collect_path, builder);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
s2 = gsk_path_to_string (path2);
|
||||
|
||||
g_assert_cmpstr (sp, ==, s2);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
g_free (s2);
|
||||
}
|
||||
|
||||
/* Test the basics of the path point api */
|
||||
typedef struct _GskRealPathPoint GskRealPathPoint;
|
||||
struct _GskRealPathPoint
|
||||
{
|
||||
gsize contour;
|
||||
gsize idx;
|
||||
float t;
|
||||
};
|
||||
|
||||
static void
|
||||
test_path_point (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathPoint point;
|
||||
GskRealPathPoint *rp = (GskRealPathPoint *)&point;
|
||||
gboolean ret;
|
||||
graphene_point_t pos, center;
|
||||
graphene_vec2_t t1, t2, mx;
|
||||
float curvature;
|
||||
|
||||
path = gsk_path_parse ("M 0 0 L 100 0 L 100 100 L 0 100 Z");
|
||||
|
||||
ret = gsk_path_get_start_point (path, &point);
|
||||
g_assert_true (ret);
|
||||
|
||||
g_assert_true (rp->contour == 0);
|
||||
g_assert_true (rp->idx == 1);
|
||||
g_assert_true (rp->t == 0);
|
||||
|
||||
ret = gsk_path_get_end_point (path, &point);
|
||||
g_assert_true (ret);
|
||||
|
||||
g_assert_true (rp->contour == 0);
|
||||
g_assert_true (rp->idx == 4);
|
||||
g_assert_true (rp->t == 1);
|
||||
|
||||
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (200, 200), INFINITY, &point);
|
||||
g_assert_true (ret);
|
||||
|
||||
g_assert_true (rp->contour == 0);
|
||||
g_assert_true (rp->idx == 2);
|
||||
g_assert_true (rp->t == 1);
|
||||
|
||||
gsk_path_point_get_position (path, &point, &pos);
|
||||
gsk_path_point_get_tangent (path, &point, GSK_PATH_START, &t1);
|
||||
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t2);
|
||||
curvature = gsk_path_point_get_curvature (path, &point, ¢er);
|
||||
|
||||
g_assert_true (graphene_point_equal (&pos, &GRAPHENE_POINT_INIT (100, 100)));
|
||||
g_assert_true (graphene_vec2_equal (&t1, graphene_vec2_y_axis ()));
|
||||
graphene_vec2_negate (graphene_vec2_x_axis (), &mx);
|
||||
g_assert_true (graphene_vec2_equal (&t2, &mx));
|
||||
g_assert_true (curvature == 0);
|
||||
|
||||
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (100, 50), INFINITY, &point);
|
||||
g_assert_true (ret);
|
||||
|
||||
g_assert_true (rp->contour == 0);
|
||||
g_assert_true (rp->idx == 2);
|
||||
g_assert_true (rp->t == 0.5);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
/* Test that gsk_path_builder_add_segment yields the expected results */
|
||||
static void
|
||||
test_path_segments (void)
|
||||
{
|
||||
struct {
|
||||
const char *path;
|
||||
graphene_point_t p1;
|
||||
graphene_point_t p2;
|
||||
const char *result;
|
||||
} tests[] = {
|
||||
{
|
||||
"M 0 0 L 100 0 L 50 50 Z",
|
||||
{ 100, 0 },
|
||||
{ 50, 50 },
|
||||
"M 100 0 L 50 50"
|
||||
},
|
||||
{
|
||||
"M 0 0 L 100 0 L 50 50 Z",
|
||||
{ 50, 0 },
|
||||
{ 70, 0 },
|
||||
"M 50 0 L 70 0"
|
||||
},
|
||||
{
|
||||
"M 0 0 L 100 0 L 50 50 Z",
|
||||
{ 70, 0 },
|
||||
{ 50, 0 },
|
||||
"M 70 0 L 100 0 L 50 50 L 0 0 L 50 0"
|
||||
},
|
||||
{
|
||||
"M 0 0 L 100 0 L 50 50 Z",
|
||||
{ 50, 0 },
|
||||
{ 50, 50 },
|
||||
"M 50 0 L 100 0 L 50 50"
|
||||
},
|
||||
{
|
||||
"M 0 0 L 100 0 L 50 50 Z",
|
||||
{ 100, 0 },
|
||||
{ 100, 0 },
|
||||
"M 100 0 L 50 50 L 0 0 L 100 0"
|
||||
}
|
||||
};
|
||||
|
||||
for (unsigned int i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathPoint p1, p2;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *result;
|
||||
char *str;
|
||||
|
||||
path = gsk_path_parse (tests[i].path);
|
||||
g_assert_true (gsk_path_get_closest_point (path, &tests[i].p1, INFINITY, &p1));
|
||||
g_assert_true (gsk_path_get_closest_point (path, &tests[i].p2, INFINITY, &p2));
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, path, &p1, &p2);
|
||||
result = gsk_path_builder_free_to_path (builder);
|
||||
str = gsk_path_to_string (result);
|
||||
|
||||
g_assert_cmpstr (str, ==, tests[i].result);
|
||||
|
||||
g_free (str);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_bad_in_fill (void)
|
||||
{
|
||||
GskPath *path;
|
||||
gboolean inside;
|
||||
|
||||
/* A fat Cantarell W */
|
||||
path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
|
||||
|
||||
/* The midpoint of the right foot of a fat Cantarell X */
|
||||
inside = gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
|
||||
|
||||
g_assert_false (inside);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
/* Test that path_in_fill implicitly closes contours. I think this is wrong,
|
||||
* but it is what "everybody" does.
|
||||
*/
|
||||
static void
|
||||
test_unclosed_in_fill (void)
|
||||
{
|
||||
GskPath *path;
|
||||
|
||||
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0 Z");
|
||||
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
|
||||
gsk_path_unref (path);
|
||||
|
||||
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0");
|
||||
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
/* Test that all the gsk_path_builder_add methods close the current
|
||||
* contour in the end and do not change the current point.
|
||||
*/
|
||||
static void
|
||||
test_path_builder_add (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path, *path2;
|
||||
char *s;
|
||||
cairo_path_t *cpath;
|
||||
cairo_surface_t *surface;
|
||||
cairo_t *cr;
|
||||
PangoLayout *layout;
|
||||
GskPathPoint point1, point2;
|
||||
|
||||
#define N_ADD_METHODS 8
|
||||
|
||||
path = gsk_path_parse ("M 10 10 L 100 100");
|
||||
|
||||
gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (50, 50), INFINITY, &point1);
|
||||
gsk_path_get_end_point (path, &point2);
|
||||
|
||||
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
|
||||
cr = cairo_create (surface);
|
||||
cairo_move_to (cr, 10, 10);
|
||||
cairo_line_to (cr, 20, 30);
|
||||
cpath = cairo_copy_path (cr);
|
||||
|
||||
layout = pango_cairo_create_layout (cr);
|
||||
pango_layout_set_text (layout, "ABC", -1);
|
||||
|
||||
for (unsigned int i = 0; i < N_ADD_METHODS; i++)
|
||||
{
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_move_to (builder, 123, 456);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_add_path (builder, path);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_add_reverse_path (builder, path);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
gsk_path_builder_add_segment (builder, path, &point1, &point2);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
gsk_path_builder_add_cairo_path (builder, cpath);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
gsk_path_builder_add_layout (builder, layout);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 10, 10));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
{
|
||||
GskRoundedRect rect;
|
||||
|
||||
gsk_rounded_rect_init (&rect,
|
||||
&GRAPHENE_RECT_INIT (0, 0, 100, 100),
|
||||
&GRAPHENE_SIZE_INIT (10, 20),
|
||||
&GRAPHENE_SIZE_INIT (20, 30),
|
||||
&GRAPHENE_SIZE_INIT (0, 0),
|
||||
&GRAPHENE_SIZE_INIT (10, 10));
|
||||
gsk_path_builder_add_rounded_rect (builder, &rect);
|
||||
}
|
||||
break;
|
||||
|
||||
case 7:
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 10);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
gsk_path_builder_rel_line_to (builder, 10, 0);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
s = gsk_path_to_string (path2);
|
||||
g_assert_true (g_str_has_prefix (s, "M 123 456"));
|
||||
g_assert_true (g_str_has_suffix (s, "M 123 456 L 133 456"));
|
||||
g_free (s);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
g_object_unref (layout);
|
||||
|
||||
cairo_path_destroy (cpath);
|
||||
cairo_destroy (cr);
|
||||
cairo_surface_destroy (surface);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/path/rsvg-parse", test_rsvg_parse);
|
||||
g_test_add_func ("/path/empty", test_empty_path);
|
||||
g_test_add_func ("/path/rect", test_rect_path);
|
||||
g_test_add_func ("/path/foreach", test_foreach);
|
||||
g_test_add_func ("/path/point", test_path_point);
|
||||
g_test_add_func ("/path/segments", test_path_segments);
|
||||
g_test_add_func ("/path/bad-in-fill", test_bad_in_fill);
|
||||
g_test_add_func ("/path/unclosed-in-fill", test_unclosed_in_fill);
|
||||
g_test_add_func ("/path/builder/add", test_path_builder_add);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
757
testsuite/gsk/path.c
Normal file
@@ -0,0 +1,757 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static GskPath *
|
||||
create_random_degenerate_path (guint max_contours)
|
||||
{
|
||||
#define N_DEGENERATE_PATHS 14
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
|
||||
{
|
||||
case 0:
|
||||
/* empty path */
|
||||
break;
|
||||
|
||||
case 1:
|
||||
/* a single point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
/* N points */
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
/* 1 closed point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
/* the same point closed N times */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
/* a zero-width and zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0, 0));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
/* a zero-width rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0,
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 7:
|
||||
/* a zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0));
|
||||
break;
|
||||
|
||||
case 8:
|
||||
/* a negative-size rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 0),
|
||||
g_test_rand_double_range (-1000, 0)));
|
||||
break;
|
||||
|
||||
case 9:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 10:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 11:
|
||||
/* an absolutely random circle */
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 12:
|
||||
/* a zero-length line */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_line_to (builder, point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
/* a cubic with start == end */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case N_DEGENERATE_PATHS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours);
|
||||
|
||||
static void
|
||||
add_shape_contour (GskPathBuilder *builder)
|
||||
{
|
||||
#define N_SHAPE_CONTOURS 3
|
||||
switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (1, 1000),
|
||||
g_test_rand_double_range (1, 1000)));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
GskPath *path = create_random_path (1);
|
||||
gsk_path_builder_add_path (builder, path);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
break;
|
||||
|
||||
case N_SHAPE_CONTOURS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_standard_contour (GskPathBuilder *builder)
|
||||
{
|
||||
guint i, n;
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
else
|
||||
gsk_path_builder_rel_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
|
||||
/* that 20 is random, but should be enough to get some
|
||||
* crazy self-intersecting shapes */
|
||||
n = g_test_rand_int_range (1, 20);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
switch (g_test_rand_int_range (0, 6))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_rel_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
gsk_path_builder_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
gsk_path_builder_rel_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
gsk_path_builder_rel_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
guint i, n;
|
||||
|
||||
/* 5% chance for a weird shape */
|
||||
if (!g_test_rand_int_range (0, 20))
|
||||
return create_random_degenerate_path (max_contours);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
n = g_test_rand_int_range (1, 10);
|
||||
n = MIN (n, max_contours);
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
/* 2/3 of shapes are standard contours */
|
||||
if (g_test_rand_int_range (0, 3))
|
||||
add_standard_contour (builder);
|
||||
else
|
||||
add_shape_contour (builder);
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GskPathOperation op;
|
||||
graphene_point_t pts[4];
|
||||
} PathOperation;
|
||||
|
||||
static void
|
||||
_g_string_append_double (GString *string,
|
||||
double d)
|
||||
{
|
||||
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
||||
|
||||
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d);
|
||||
g_string_append (string, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
_g_string_append_point (GString *string,
|
||||
const graphene_point_t *pt)
|
||||
{
|
||||
_g_string_append_double (string, pt->x);
|
||||
g_string_append_c (string, ' ');
|
||||
_g_string_append_double (string, pt->y);
|
||||
}
|
||||
|
||||
static void
|
||||
path_operation_print (const PathOperation *p,
|
||||
GString *string)
|
||||
{
|
||||
switch (p->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
g_string_append (string, "M ");
|
||||
_g_string_append_point (string, &p->pts[0]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
g_string_append (string, " Z");
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
g_string_append (string, " L ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
g_string_append (string, " Q ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
g_string_append (string, " C ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[3]);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
path_operation_equal (const PathOperation *p1,
|
||||
const PathOperation *p2,
|
||||
float epsilon)
|
||||
{
|
||||
if (p1->op != p2->op)
|
||||
return FALSE;
|
||||
|
||||
/* No need to compare pts[0] for most ops, that's just
|
||||
* duplicate work. */
|
||||
switch (p1->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
return graphene_point_near (&p1->pts[0], &p2->pts[0], epsilon);
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
case GSK_PATH_CLOSE:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon);
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon);
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
|
||||
&& graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon);
|
||||
|
||||
default:
|
||||
g_return_val_if_reached (FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_path_operation_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_array_append_vals (user_data,
|
||||
(PathOperation[1]) { {
|
||||
op,
|
||||
{
|
||||
GRAPHENE_POINT_INIT(pts[0].x, pts[0].y),
|
||||
GRAPHENE_POINT_INIT(n_pts > 1 ? pts[1].x : 0,
|
||||
n_pts > 1 ? pts[1].y : 0),
|
||||
GRAPHENE_POINT_INIT(n_pts > 2 ? pts[2].x : 0,
|
||||
n_pts > 2 ? pts[2].y : 0),
|
||||
GRAPHENE_POINT_INIT(n_pts > 3 ? pts[3].x : 0,
|
||||
n_pts > 3 ? pts[3].y : 0)
|
||||
},
|
||||
} },
|
||||
1);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GArray *
|
||||
collect_path (GskPath *path)
|
||||
{
|
||||
GArray *array = g_array_new (FALSE, FALSE, sizeof (PathOperation));
|
||||
|
||||
/* Use -1 here because we want all the flags, even future additions */
|
||||
gsk_path_foreach (path, -1, collect_path_operation_cb, array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static void
|
||||
assert_path_equal_func (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
GskPath *path1,
|
||||
GskPath *path2,
|
||||
float epsilon)
|
||||
{
|
||||
GArray *ops1, *ops2;
|
||||
guint i;
|
||||
|
||||
ops1 = collect_path (path1);
|
||||
ops2 = collect_path (path2);
|
||||
|
||||
for (i = 0; i < MAX (ops1->len, ops2->len); i++)
|
||||
{
|
||||
PathOperation *op1 = i < ops1->len ? &g_array_index (ops1, PathOperation, i) : NULL;
|
||||
PathOperation *op2 = i < ops2->len ? &g_array_index (ops2, PathOperation, i) : NULL;
|
||||
|
||||
if (op1 == NULL || op2 == NULL || !path_operation_equal (op1, op2, epsilon))
|
||||
{
|
||||
GString *string;
|
||||
guint j;
|
||||
|
||||
/* Find the operation we start to print */
|
||||
for (j = i; j-- > 0; )
|
||||
{
|
||||
PathOperation *op = &g_array_index (ops1, PathOperation, j);
|
||||
if (op->op == GSK_PATH_MOVE)
|
||||
break;
|
||||
if (j + 3 == i)
|
||||
{
|
||||
j = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string = g_string_new (j == 0 ? "" : "... ");
|
||||
for (; j < i; j++)
|
||||
{
|
||||
PathOperation *op = &g_array_index (ops1, PathOperation, j);
|
||||
path_operation_print (op, string);
|
||||
g_string_append_c (string, ' ');
|
||||
}
|
||||
|
||||
g_string_append (string, "\\\n ");
|
||||
if (op1)
|
||||
{
|
||||
path_operation_print (op1, string);
|
||||
if (ops1->len > i + 1)
|
||||
g_string_append (string, " ...");
|
||||
}
|
||||
g_string_append (string, "\n ");
|
||||
if (op1)
|
||||
{
|
||||
path_operation_print (op2, string);
|
||||
if (ops2->len > i + 1)
|
||||
g_string_append (string, " ...");
|
||||
}
|
||||
|
||||
g_assertion_message (domain, file, line, func, string->str);
|
||||
|
||||
g_string_free (string, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
g_array_free (ops1, TRUE);
|
||||
g_array_free (ops2, TRUE);
|
||||
}
|
||||
#define assert_path_equal(p1,p2) assert_path_equal_func(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (p1),(p2), FLOAT_EPSILON)
|
||||
#define assert_path_equal_with_epsilon(p1,p2, epsilon) \
|
||||
assert_path_equal_func(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (p1),(p2), (epsilon))
|
||||
|
||||
static void
|
||||
test_create (void)
|
||||
{
|
||||
GskPath *path1, *path2, *built;
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
char *s;
|
||||
GString *str;
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
builder = gsk_path_builder_new ();
|
||||
path1 = create_random_path (G_MAXUINT);
|
||||
gsk_path_builder_add_path (builder, path1);
|
||||
path2 = create_random_path (G_MAXUINT);
|
||||
gsk_path_builder_add_path (builder, path2);
|
||||
built = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
str = g_string_new (NULL);
|
||||
gsk_path_print (path1, str);
|
||||
if (!gsk_path_is_empty (path1) && !gsk_path_is_empty (path2))
|
||||
g_string_append_c (str, ' ');
|
||||
gsk_path_print (path2, str);
|
||||
|
||||
s = gsk_path_to_string (built);
|
||||
|
||||
g_assert_cmpstr (s, ==, str->str);
|
||||
g_string_free (str, TRUE);
|
||||
g_free (s);
|
||||
|
||||
gsk_path_unref (built);
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
char *string1, *string2;
|
||||
|
||||
path1 = create_random_path (G_MAXUINT);
|
||||
string1 = gsk_path_to_string (path1);
|
||||
g_assert_nonnull (string1);
|
||||
|
||||
path2 = gsk_path_parse (string1);
|
||||
g_assert_nonnull (path2);
|
||||
string2 = gsk_path_to_string (path2);
|
||||
g_assert_nonnull (string2);
|
||||
|
||||
assert_path_equal_with_epsilon (path1, path2, 1.f / 1024);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
g_free (string2);
|
||||
g_free (string1);
|
||||
}
|
||||
}
|
||||
|
||||
#define N_PATHS 3
|
||||
static void
|
||||
test_in_fill_union (void)
|
||||
{
|
||||
GskPath *path, *paths[N_PATHS];
|
||||
GskPathBuilder *builder;
|
||||
guint i, j, k;
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
{
|
||||
paths[k] = create_random_path (G_MAXUINT);
|
||||
gsk_path_builder_add_path (builder, paths[k]);
|
||||
}
|
||||
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
GskFillRule fill_rule;
|
||||
|
||||
for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
|
||||
{
|
||||
guint n_in_fill = 0;
|
||||
gboolean in_fill;
|
||||
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
{
|
||||
if (gsk_path_in_fill (paths[k], &test, GSK_FILL_RULE_EVEN_ODD))
|
||||
n_in_fill++;
|
||||
}
|
||||
|
||||
in_fill = gsk_path_in_fill (path, &test, GSK_FILL_RULE_EVEN_ODD);
|
||||
switch (fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
if (n_in_fill == 0)
|
||||
g_assert_false (in_fill);
|
||||
else if (n_in_fill == 1)
|
||||
g_assert_true (in_fill);
|
||||
/* else we can't say anything because the winding rule doesn't give enough info */
|
||||
break;
|
||||
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
g_assert_cmpint (in_fill, ==, n_in_fill & 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gsk_path_unref (path);
|
||||
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
gsk_path_unref (paths[k]);
|
||||
}
|
||||
}
|
||||
#undef N_PATHS
|
||||
|
||||
/* This is somewhat sucky because using foreach breaks up the contours
|
||||
* (like rects and circles) and replaces everything with the standard
|
||||
* contour.
|
||||
* But at least it extensively tests the standard contour.
|
||||
*/
|
||||
static gboolean
|
||||
rotate_path_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder **builders = user_data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
|
||||
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builders[0]);
|
||||
gsk_path_builder_close (builders[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_in_fill_rotated (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathBuilder *builders[2];
|
||||
GskPath *paths[2];
|
||||
guint i, j;
|
||||
|
||||
#define N_FILL_RULES 2
|
||||
/* if this triggers, you added a new enum value to GskFillRule, so the define above needs
|
||||
* an update */
|
||||
g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
path = create_random_path (G_MAXUINT);
|
||||
builders[0] = gsk_path_builder_new ();
|
||||
builders[1] = gsk_path_builder_new ();
|
||||
/* Use -1 here because we want all the flags, even future additions */
|
||||
gsk_path_foreach (path, -1, rotate_path_cb, builders);
|
||||
gsk_path_unref (path);
|
||||
|
||||
paths[0] = gsk_path_builder_free_to_path (builders[0]);
|
||||
paths[1] = gsk_path_builder_free_to_path (builders[1]);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
|
||||
float x = g_test_rand_double_range (-1000, 1000);
|
||||
float y = g_test_rand_double_range (-1000, 1000);
|
||||
|
||||
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
|
||||
==,
|
||||
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
|
||||
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (y, x), fill_rule),
|
||||
==,
|
||||
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (x, -y), fill_rule));
|
||||
}
|
||||
|
||||
gsk_path_unref (paths[0]);
|
||||
gsk_path_unref (paths[1]);
|
||||
}
|
||||
#undef N_FILL_RULES
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/path/create", test_create);
|
||||
g_test_add_func ("/path/parse", test_parse);
|
||||
g_test_add_func ("/path/in-fill-union", test_in_fill_union);
|
||||
g_test_add_func ("/path/in-fill-rotated", test_in_fill_rotated);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -10,7 +10,7 @@ test_calendar_set_day (void)
|
||||
|
||||
cal = gtk_calendar_new ();
|
||||
|
||||
tz = g_time_zone_new_identifier ("MET");
|
||||
tz = g_time_zone_new_identifier ("Europe/Brussels");
|
||||
g_assert_nonnull (tz);
|
||||
dt = g_date_time_new (tz, 1970, 3, 1, 0, 0, 0);
|
||||
g_assert_nonnull (dt);
|
||||
|
||||
@@ -130,6 +130,14 @@ count_nodes (GskRenderNode *node,
|
||||
d = MAX (d, dd);
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
count_nodes (gsk_fill_node_get_child (node), counts, &d);
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
count_nodes (gsk_stroke_node_get_child (node), counts, &d);
|
||||
break;
|
||||
|
||||
case GSK_NOT_A_RENDER_NODE:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
|
||||