Compare commits

...

40 Commits

Author SHA1 Message Date
Matthias Clasen
5eda60e322 Pencil in the no-LUT approach
If we don't use a LUT, we have to handle the
one curve the point in question falls in every
time, which is some overhead, but it lets us
compute precise values.
2023-08-12 09:04:34 -04:00
Matthias Clasen
64e73668a8 curve: Allow specifying epsilon for length->t
Passing FLT_EPSILON gets vert precise results
at a high cost.
2023-08-12 07:43:45 -04:00
Matthias Clasen
9edc4b0854 Add curve length tests 2023-08-12 07:25:02 -04:00
Matthias Clasen
2e86373b0c curve: Improve accuracy of length->t 2023-08-12 07:24:27 -04:00
Matthias Clasen
aed0588fd5 Add some diagnostics for curve length vs splitting 2023-08-12 07:24:12 -04:00
Matthias Clasen
ab492e7817 wip: Redo measures
Use the new arclength functions of GskCurve.

Currently, this is not using a lookup table
for values inside a curve, but just decomposes
on the fly.
2023-08-10 07:45:43 -04:00
Matthias Clasen
427c95927e curve: Add arclength functions
Use Legendre-Gauss approximation for the
arclength integral, and use bisection for
finding a t value for a given arc length.
2023-08-10 06:41:31 -04:00
Benjamin Otte
6aa76aa35a demos: Add a text-on-path demo 2023-08-09 23:45:11 -04:00
Matthias Clasen
bdaa7bf1d0 Add tests for GskPathMeasure 2023-08-09 23:45:11 -04:00
Matthias Clasen
7554cc1b6f Add GskPathMeasure
GskPathMeasure is an auxiliary object for
measuring path lengths.
2023-08-09 23:40:53 -04:00
Matthias Clasen
d1e1dc5b56 curve: Change decomposition
Remove the 'curviness' criteria, since they lead
us astray. Instead check for distance and t difference.
This is enough to make the 'bad split' test pass again.
2023-08-09 23:40:40 -04:00
Matthias Clasen
267367355f Merge branch 'path-fill-tests2' into 'main'
Add more path fill tests

See merge request GNOME/gtk!6268
2023-08-10 03:10:14 +00:00
Matthias Clasen
0aa3356499 Add more path fill tests
These also used to live with measure tests.
2023-08-09 22:47:13 -04:00
Matthias Clasen
14a3b35333 Merge branch 'path-fill-tests' into 'main'
Add path fill tests

See merge request GNOME/gtk!6267
2023-08-10 02:21:34 +00:00
Matthias Clasen
77def95659 Add path fill tests
These used to live with the measure tests, but
path_in_fill no longer involves a measure.
2023-08-09 21:50:29 -04:00
Matthias Clasen
4e91cd1155 Merge branch 'matthiasc/for-main' into 'main'
pathpoint: Use the right type

See merge request GNOME/gtk!6266
2023-08-09 20:37:14 +00:00
Matthias Clasen
2d923d362f Merge branch 'bilelmoussaoui/gi-gsk' into 'main'
gsk/gi: new Path api fixes

See merge request GNOME/gtk!6265
2023-08-09 19:40:53 +00:00
Matthias Clasen
b7544636d4 contour: Skip the move
When jumping between segments, we always
need to skip index 0, since thats the move.

This fixes the tangent checks in the
rectangle tests.
2023-08-09 15:12:58 -04:00
Matthias Clasen
6220baf230 get_closest_point: Prefer earlier points
Only switch to a later point if it is strictly
closer than the existing match. This fixes
start- vs endpoint confusion in the rectangle
tests.
2023-08-09 15:12:52 -04:00
Matthias Clasen
917307a6a4 pathpoint: Use the right type 2023-08-09 14:37:14 -04:00
Matthias Clasen
5f49b7b492 Merge branch 'matthiasc/for-main' into 'main'
pathpoint: Clarify docs

See merge request GNOME/gtk!6262
2023-08-09 18:36:05 +00:00
Bilal Elmoussaoui
b03abea402 gsk: Don't change the GskRenderNodeType order
As we use their values to check that a node can be downcasted to a node
type in the Rust bindings
such change would break existing code
2023-08-09 20:26:46 +02:00
Bilal Elmoussaoui
d4fcc883cb gsk/gi: Add missing since annotations & docs 2023-08-09 20:26:46 +02:00
Daniel Boles
ca2a2697d6 Merge branch 'dboles/for-main' into 'main'
docs: Add/fix some links; avoid pluralisation

See merge request GNOME/gtk!6261
2023-08-09 16:57:19 +00:00
Matthias Clasen
68c6f63e90 pathpoint: Clarify docs
Add a paragraph explaining the situation
for comparing the start- and endpoint of
a closed path.
2023-08-09 11:30:26 -04:00
Daniel Boles
0e89fed8ed docs: Add/fix some links; avoid pluralisation
Appending `s` breaks the [type@NS.Object] notation, so fix that in
ListHeader. Add links to ListItem and Overlay, and avoid appending `s`
after `backtick`s just for consistency with the [type@NS.Object] issue.
2023-08-09 15:35:12 +01:00
Benjamin Otte
4d726543d0 Merge branch 'path-for-merge-6' into 'main'
Simplify GskPathPoint

See merge request GNOME/gtk!6260
2023-08-09 14:24:33 +00:00
Matthias Clasen
2bcc998e90 Add tests for gsk_path_builder_add_segment
Test at least some of the special cases:
- start and end point on the same contour
- start and end point on the same segment
- end before start
2023-08-09 07:01:01 -04:00
Matthias Clasen
f707185248 Add gsk_path_builder_add_segment
With GskPathPoint, we can subset paths without
a measure object.
2023-08-09 07:01:01 -04:00
Matthias Clasen
ded30049db path: Add gsk_path_get_start/end_point
These are useful to have, now that we are
relying more on GskPathPoint in our api.
2023-08-08 22:05:34 -04:00
Matthias Clasen
5c55e9d11e Add gsk_path_point_equal/compare
These just make sense to have.
2023-08-08 22:05:34 -04:00
Matthias Clasen
c35bb11c18 Simplify GskPathPoint
Use simple representation of just
{ contour index, segment index, t value }
2023-08-08 20:31:56 -04:00
Matthias Clasen
cc19781d27 Merge branch 'cpdb-fix' into 'main'
print: Update printerprivate header location for cpdb backend

See merge request GNOME/gtk!6259
2023-08-08 23:01:12 +00:00
Jeremy Bícha
08d08678e8 print: Update printerprivate header location for cpdb backend
This fixes the print-cpdb build option
2023-08-08 17:55:54 -04:00
Matthias Clasen
11b632a691 Merge branch 'timezone' into 'main'
test-calendar: Update timezone name

See merge request GNOME/gtk!6258
2023-08-08 19:31:31 +00:00
Jeremy Bícha
4881ef251e test-calendar: Update timezone name
Debian no longer provides the MET timezone
but instead provides geographical region and city name
2023-08-08 15:01:42 -04:00
Luca Bacci
5b39abd1a4 Merge branch 'fix-server-side-decorations' into 'main'
win32: Fix minimize and maximize buttons missing on ssd

Closes #5142

See merge request GNOME/gtk!6052
2023-08-08 15:17:09 +00:00
Daniel Mustieles
f0089097dd Update Spanish translation 2023-08-08 10:41:17 +00:00
Fran Dieguez
d603925ab2 Update Galician translation 2023-08-08 00:05:18 +00:00
dgsasha
5240d8cdf5 win32: Fix minimize and maximize buttons missing on ssd
Completely fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/5142
2023-06-10 23:08:21 -04:00
42 changed files with 5447 additions and 1784 deletions

150
LOG Normal file
View File

@@ -0,0 +1,150 @@
TAP version 13
# random seed: R02S22611f6aefc1121b0ab2dc5286960449
# GLib-GIO-DEBUG: _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for gsettings-backend
# GLib-GIO-DEBUG: Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 2.73.3)
# GLib-GIO-DEBUG: Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 2.73.3)
1..1
# Start of ops tests
# testcase 0 op 0
collecting#
Cubic 0: # M 100.000000 100.000000 C 150.000000 100.000000 200.000000 100.000000 250.000000 100.000000
Cubic 1: # M 250.000000 100.000000 C 300.000000 100.000000 350.000000 100.000000 400.000000 100.000000
Line 2: # M 400 100 L 200 300
Line 3: # M 200 300 L 100 100
Cubic 4: # M 200.000000 100.000000 C 250.000000 100.000000 300.000000 100.000000 350.000000 100.000000
Cubic 5: # M 350.000000 100.000000 C 400.000000 100.000000 450.000000 100.000000 500.000000 100.000000
Line 6: # M 500 100 L 300 300
Line 7: # M 300 300 L 200 100
splitting#
1 intersections between Cubic 0 and Cubic 1#
1 intersections between Cubic 0 and Line 3#
9 intersections between Cubic 0 and Cubic 4#
# split Cubic 0.0 from Cubic 0 at 0.666666: M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
split Cubic 0.1 from Cubic 0 at 0.666666: M 199.999893 100.000000 C 216.666595 100.000000 233.333298 100.000000 250.000000 100.000000split Cubic 0.4 from Cubic 0 at 0.00600814: M 200.300323 100.000000 C 216.866882 100.000000 233.433441 100.000000 250.000000 100.000000split Cubic 0.7 from Cubic 0 at 0.00604445: M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000# split Cubic 4.9 from Cubic 4 at 0.00533867: M 200.800812 100.000000 C 250.533875 100.000000 300.266937 100.000000 350.000000 100.000000
1 intersections between Cubic 0.1 and Line 7#
1 intersections between Cubic 1 and Line 2#
9 intersections between Cubic 1 and Cubic 4.9#
split Cubic 1.9 from Cubic 1 at 0.00534248: M 250.801361 100.000000 C 300.534241 100.000000 350.267120 100.000000 400.000000 100.000000# split Cubic 4.9.0 from Cubic 4.9 at 0.329756: M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000
# split Cubic 4.9.1 from Cubic 4.9 at 0.329756: M 250.000122 100.000000 C 283.333435 100.000000 316.666718 100.000000 350.000000 100.000000
# split Cubic 4.9.6 from Cubic 4.9 at 0.00500283: M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000
9 intersections between Cubic 1.9 and Cubic 5#
# split Cubic 1.9.0 from Cubic 1.9 at 0.664877: M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000
split Cubic 1.9.1 from Cubic 1.9 at 0.664877: M 350.000122 100.000000 C 366.666748 100.000000 383.333374 100.000000 400.000000 100.000000split Cubic 1.9.4 from Cubic 1.9 at 0.00601021: M 350.300598 100.000000 C 366.867065 100.000000 383.433533 100.000000 400.000000 100.000000split Cubic 1.9.7 from Cubic 1.9 at 0.00604655: M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000# split Cubic 5.9 from Cubic 5 at 0.00534248: M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
1 intersections between Line 2 and Line 3#
1 intersections between Line 2 and Line 7#
# split Line 2.0 from Line 2 at 0.666667: M 400 100 L 266.667 233.333
split Line 2.1 from Line 2 at 0.666667: M 266.667 233.333 L 200 300# split Line 7.0 from Line 7 at 0.333333: M 300 300 L 266.667 233.333
# split Line 7.1 from Line 7 at 0.333333: M 266.667 233.333 L 200 100
1 intersections between Cubic 4 and Cubic 4.9.0#
1 intersections between Cubic 4 and Line 7.1#
1 intersections between Cubic 4.9.0 and Cubic 4.9.1#
1 intersections between Cubic 4.9.1 and Cubic 4.9.6#
1 intersections between Cubic 4.9.6 and Cubic 5#
1 intersections between Cubic 5 and Cubic 5.9#
1 intersections between Cubic 5.9 and Line 6#
1 intersections between Line 6 and Line 7.0#
Cubic 0.0: # M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
Cubic 0.1: # M 199.999893 100.000000 C 200.100037 100.000000 200.200180 100.000000 200.300323 100.000000
Cubic 0.4: # M 200.300323 100.000000 C 200.400452 100.000000 200.500580 100.000000 200.600708 100.000000
Cubic 0.7: # M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000
Cubic 1: # M 250.000000 100.000000 C 250.267120 100.000000 250.534241 100.000000 250.801361 100.000000
Cubic 1.9.0: # M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000
Cubic 1.9.1: # M 350.000122 100.000000 C 350.100281 100.000000 350.200439 100.000000 350.300598 100.000000
Cubic 1.9.4: # M 350.300598 100.000000 C 350.400757 100.000000 350.500916 100.000000 350.601074 100.000000
Cubic 1.9.7: # M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000
Line 2.0: # M 400 100 L 266.667 233.333
Line 2.1: # M 266.667 233.333 L 200 300
Line 3: # M 200 300 L 100 100
Cubic 4: # M 200.000000 100.000000 C 200.266937 100.000000 200.533875 100.000000 200.800812 100.000000
Cubic 4.9.0: # M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000
Cubic 4.9.1: # M 250.000122 100.000000 C 250.166885 100.000000 250.333649 100.000000 250.500412 100.000000
Cubic 4.9.6: # M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000
Cubic 5: # M 350.000000 100.000000 C 350.267120 100.000000 350.534241 100.000000 350.801361 100.000000
Cubic 5.9: # M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
Line 6: # M 500 100 L 300 300
Line 7.0: # M 300 300 L 266.667 233.333
Line 7.1: # M 266.667 233.333 L 200 100
classifying#
01 Cubic 0.0: # M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
[11 Cubic 0.7: # M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000 ]
[11 Cubic 1.9.0: # M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000 ]
[11 Cubic 1.9.7: # M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000 ]
[11 Line 2.0: # M 400 100 L 266.667 233.333 ]
01 Line 2.1: # M 266.667 233.333 L 200 300
01 Line 3: # M 200 300 L 100 100
[11 Cubic 4: # M 200.000000 100.000000 C 200.266937 100.000000 200.533875 100.000000 200.800812 100.000000 ]
[11 Cubic 4.9.0: # M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000 ]
[11 Cubic 4.9.6: # M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000 ]
[11 Cubic 5: # M 350.000000 100.000000 C 350.267120 100.000000 350.534241 100.000000 350.801361 100.000000 ]
01 Cubic 5.9: # M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
01 Line 6: # M 500 100 L 300 300
01 Line 7.0: # M 300 300 L 266.667 233.333
[11 Line 7.1: # M 266.667 233.333 L 200 100 ]
fixups#
# found 3 bad nodes
# split Cubic 0/Cubic 4 BAD 200.600723 100.000000
# [11 Line 7.1 ] 116.565
# [11 Cubic 0.7 ] 180
# [11 Cubic 4 ] 180
# [11 Cubic 4 ] 180
# [11 Cubic 4.9.0 ] 180
# >01 Cubic 0.0 360
# split Cubic 1/Cubic 4.9 BAD 250.801376 100.000000
# [11 Cubic 1.9.0 ] 180
# [11 Cubic 4.9.6 ] 180
# [11 Cubic 0.7 ] 360
# [11 Cubic 4.9.0 ] 360
# split Cubic 1.9/Cubic 5 BAD 350.601105 100.000000
# [11 Cubic 1.9.7 ] 180
# [11 Cubic 5 ] 180
# [11 Cubic 5 ] 180
# <01 Cubic 5.9 180
# [11 Cubic 1.9.0 ] 360
# [11 Cubic 4.9.6 ] 360
reassembling#
start new contour Cubic 0.0#
# Cubic 0.0 ends at:
# start 0 100.000000 100.000000
# >01 Line 3 116.565
# (10 Cubic 0.0 ) 180
picking cw#
append Line 3#
# Line 3 ends at:
# end 2 200.000000 300.000000
# >01 Line 2.1 225
# (10 Line 3 ) 296.565
picking cw#
append Line 2.1#
# Line 2.1 ends at:
# split Line 2/Line 7 266.666656 233.333344
# (10 Line 2.1 ) 45
# >01 Line 7.0 116.565
# [11 Line 2.0 ] 225
# [11 Line 7.1 ] 296.565
picking cw#
append Line 7.0#
# Line 7.0 ends at:
# end 6 300.000000 300.000000
# >01 Line 6 225
# (10 Line 7.0 ) 296.565
picking cw#
append Line 6#
# Line 6 ends at:
# end 5 500.000000 100.000000
# (10 Line 6 ) 45
# >01 Cubic 5.9 360
picking cw#
append Cubic 5.9#
# Cubic 5.9 ends at:
# split Cubic 1.9/Cubic 5 BAD 350.601105 100.000000
# [11 Cubic 1.9.7 ] 180
# [11 Cubic 5 ] 180
# [11 Cubic 5 ] 180
# (10 Cubic 5.9 ) 180
# [11 Cubic 1.9.0 ] 360
# [11 Cubic 4.9.6 ] 360
picking cw#
**
ERROR:../testsuite/gsk/path-ops.c:359:test_ops_simple: assertion failed (s == tests[i].out): ("M 354.60110473632812 100 A 4 4 0 0 0 346.60110473632812 100 A 4 4 0 0 0 354.60110473632812 100 z M 252.80137634277344 100 A 2 2 0 0 0 248.80137634277344 100 A 2 2 0 0 0 252.80137634277344 100 z M 204.60072326660156 100 A 4 4 0 0 0 196.60072326660156 100 A 4 4 0 0 0 204.60072326660156 100 z" == "M 100 100 z")
not ok /ops/simple - ERROR:../testsuite/gsk/path-ops.c:359:test_ops_simple: assertion failed (s == tests[i].out): ("M 354.60110473632812 100 A 4 4 0 0 0 346.60110473632812 100 A 4 4 0 0 0 354.60110473632812 100 z M 252.80137634277344 100 A 2 2 0 0 0 248.80137634277344 100 A 2 2 0 0 0 252.80137634277344 100 z M 204.60072326660156 100 A 4 4 0 0 0 196.60072326660156 100 A 4 4 0 0 0 204.60072326660156 100 z" == "M 100 100 z")
Bail out!

View File

@@ -336,6 +336,7 @@
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>
<file>printing.c</file>
@@ -421,6 +422,9 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>

View File

@@ -73,6 +73,7 @@ demos = files([
'panes.c',
'password_entry.c',
'path_fill.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',

604
demos/gtk-demo/path_text.c Normal file
View File

@@ -0,0 +1,604 @@
/* Path/Text
*
* This demo shows how to use GskPath to animate a path along another path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_TEXT,
PROP_EDITABLE,
N_PROPS
};
struct _GtkPathWidget
{
GtkWidget parent_instance;
char *text;
gboolean editable;
graphene_point_t points[4];
guint active_point;
float line_closest;
GskPath *line_path;
GskPathMeasure *line_measure;
GskPath *text_path;
GdkPaintable *background;
};
struct _GtkPathWidgetClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
static GskPath *
create_path_from_text (GtkWidget *widget,
const char *text,
graphene_point_t *out_offset)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
GskPath *result;
layout = gtk_widget_create_pango_layout (widget, text);
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
result = gsk_path_builder_free_to_path (builder);
if (out_offset)
graphene_point_init (out_offset, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
g_object_unref (layout);
return result;
}
typedef struct
{
GskPathMeasure *measure;
GskPathBuilder *builder;
graphene_point_t offset;
double scale;
} GtkPathTransform;
static void
gtk_path_transform_point (GskPathMeasure *measure,
const graphene_point_t *pt,
const graphene_point_t *offset,
float scale,
graphene_point_t *res)
{
graphene_vec2_t tangent;
GskPathPoint point;
if (gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, &point))
{
GskPath *path = gsk_path_measure_get_path (measure);
gsk_path_point_get_position (path, &point, res);
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent);
res->x -= (pt->y + offset->y) * scale * graphene_vec2_get_y (&tangent);
res->y += (pt->y + offset->y) * scale * graphene_vec2_get_x (&tangent);
}
}
static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
gpointer data)
{
GtkPathTransform *transform = data;
switch (op)
{
case GSK_PATH_MOVE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[0], &transform->offset, transform->scale, &res);
gsk_path_builder_move_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_LINE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res);
gsk_path_builder_line_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_QUAD:
{
graphene_point_t res[2];
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
gsk_path_builder_quad_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y);
}
break;
case GSK_PATH_CUBIC:
{
graphene_point_t res[3];
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
gtk_path_transform_point (transform->measure, &pts[3], &transform->offset, transform->scale, &res[2]);
gsk_path_builder_cubic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, res[2].x, res[2].y);
}
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (transform->builder);
break;
default:
g_assert_not_reached();
return FALSE;
}
return TRUE;
}
static GskPath *
gtk_path_transform (GskPathMeasure *measure,
GskPath *path,
const graphene_point_t *offset)
{
GtkPathTransform transform = { measure, gsk_path_builder_new (), *offset };
graphene_rect_t bounds;
gsk_path_get_bounds (path, &bounds);
if (bounds.origin.x + bounds.size.width > 0)
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
else
transform.scale = 1.0f;
gsk_path_foreach (path, -1, gtk_path_transform_op, &transform);
return gsk_path_builder_free_to_path (transform.builder);
}
static void
gtk_path_widget_clear_text_path (GtkPathWidget *self)
{
g_clear_pointer (&self->text_path, gsk_path_unref);
}
static void
gtk_path_widget_clear_paths (GtkPathWidget *self)
{
gtk_path_widget_clear_text_path (self);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
}
static void
gtk_path_widget_create_text_path (GtkPathWidget *self)
{
GskPath *path;
graphene_point_t offset;
gtk_path_widget_clear_text_path (self);
if (self->line_measure == NULL)
return;
path = create_path_from_text (GTK_WIDGET (self), self->text, &offset);
self->text_path = gtk_path_transform (self->line_measure, path, &offset);
gsk_path_unref (path);
}
static void
gtk_path_widget_create_paths (GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
GskPathBuilder *builder;
gtk_path_widget_clear_paths (self);
if (width <= 0 || height <= 0)
return;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder,
self->points[0].x * width, self->points[0].y * height);
gsk_path_builder_cubic_to (builder,
self->points[1].x * width, self->points[1].y * height,
self->points[2].x * width, self->points[2].y * height,
self->points[3].x * width, self->points[3].y * height);
self->line_path = gsk_path_builder_free_to_path (builder);
self->line_measure = gsk_path_measure_new (self->line_path);
gtk_path_widget_create_text_path (self);
}
static void
gtk_path_widget_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
gtk_path_widget_create_paths (self);
}
static void
gtk_path_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskPath *path;
GskStroke *stroke;
gsize i;
/* frosted glass the background */
gtk_snapshot_push_blur (snapshot, 100);
gdk_paintable_snapshot (self->background, snapshot, width, height);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.6 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the text */
if (self->text_path)
{
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
gdk_paintable_snapshot (self->background, snapshot, width, height);
/* ... with an emboss effect */
stroke = gsk_stroke_new (2.0);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gsk_stroke_free (stroke);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
if (self->editable && self->line_path)
{
GskPathBuilder *builder;
/* draw the control line */
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the points */
builder = gsk_path_builder_new ();
for (i = 0; i < 4; i++)
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
}
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
if (self->line_closest >= 0)
{
GskPathBuilder *builder;
GskPathPoint point;
graphene_point_t closest;
builder = gsk_path_builder_new ();
if (gsk_path_measure_get_point (self->line_measure, self->line_closest, &point))
{
gsk_path_point_get_position (self->line_path, &point, &closest);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
}
static void
gtk_path_widget_set_text (GtkPathWidget *self,
const char *text)
{
if (g_strcmp0 (self->text, text) == 0)
return;
g_free (self->text);
self->text = g_strdup (text);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
}
static void
gtk_path_widget_set_editable (GtkPathWidget *self,
gboolean editable)
{
if (self->editable == editable)
return;
self->editable = editable;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
}
static void
gtk_path_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
gtk_path_widget_set_text (self, g_value_get_string (value));
break;
case PROP_EDITABLE:
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, self->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_dispose (GObject *object)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
gtk_path_widget_clear_paths (self);
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
}
static void
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_widget_dispose;
object_class->set_property = gtk_path_widget_set_property;
object_class->get_property = gtk_path_widget_get_property;
widget_class->size_allocate = gtk_path_widget_allocate;
widget_class->snapshot = gtk_path_widget_snapshot;
properties[PROP_TEXT] =
g_param_spec_string ("text",
"text",
"Text transformed along a path",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"editable",
"If the path can be edited by the user",
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkPathWidget *self)
{
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
gsize i;
for (i = 0; i < 4; i++)
{
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
{
self->active_point = i;
break;
}
}
if (i == 4)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
drag_update (GtkGestureDrag *drag,
double offset_x,
double offset_y,
GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
double start_x, start_y;
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
(start_y + offset_y) / height);
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkPathWidget *self)
{
GskPathPoint point;
graphene_point_t pos;
if (gsk_path_get_closest_point (gsk_path_measure_get_path (self->line_measure),
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
&point))
{
gsk_path_point_get_position (self->line_path, &point, &pos);
self->line_closest = graphene_point_distance (&pos, &GRAPHENE_POINT_INIT (x, y), NULL, NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkPathWidget *self)
{
self->line_closest = -1;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_widget_init (GtkPathWidget *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->line_closest = -1;
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
gtk_path_widget_set_text (self, "It's almost working");
}
GtkWidget *
gtk_path_widget_new (void)
{
GtkPathWidget *self;
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_text (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WIDGET);
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Text along a Path</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="edit-toggle">
<property name="icon-name">document-edit-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<child>
<object class="GtkEntry" id="text">
<property name="text">Through the looking glass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPathWidget" id="view">
<property name="editable" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<property name="text" bind-source="text" bind-property="text" bind-flags="sync-create"></property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -22,6 +22,7 @@
#include <gsk/gskenums.h>
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
#include <gsk/gskpathmeasure.h>
#include <gsk/gskpathpoint.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>

View File

@@ -61,6 +61,7 @@ struct _GskContourClass
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,
@@ -76,6 +77,23 @@ struct _GskContourClass
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);
gpointer (* init_measure) (const GskContour *contour,
float tolerance,
float *out_length);
void (* free_measure) (const GskContour *contour,
gpointer measure_data);
void (* get_point) (const GskContour *contour,
gpointer measure_data,
float offset,
GskRealPathPoint *result);
float (* get_distance) (const GskContour *contour,
GskRealPathPoint *point,
gpointer measure_data);
};
/* {{{ Utilities */
@@ -224,6 +242,38 @@ gsk_standard_contour_reverse (const GskContour *contour)
return res;
}
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 GskPathFlags
gsk_standard_contour_get_flags (const GskContour *contour)
{
@@ -239,7 +289,7 @@ gsk_standard_contour_print (const GskContour *contour,
const GskStandardContour *self = (const GskStandardContour *) contour;
gsize i;
for (i = 0; i < self->n_ops; i ++)
for (i = 0; i < self->n_ops; i++)
{
const graphene_point_t *pt = gsk_pathop_points (self->ops[i]);
@@ -276,7 +326,7 @@ gsk_standard_contour_print (const GskContour *contour,
break;
default:
g_assert_not_reached();
g_assert_not_reached ();
return;
}
}
@@ -367,36 +417,12 @@ gsk_standard_contour_get_start_end (const GskContour *contour,
*end = self->points[self->n_points - 1];
}
static int
gsk_standard_contour_get_winding (const GskContour *contour,
const graphene_point_t *point)
static gsize
gsk_standard_contour_get_n_points (const GskContour *contour)
{
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;
return self->n_ops;
}
static gboolean
@@ -420,8 +446,8 @@ gsk_standard_contour_get_closest_point (const GskContour *contour,
if (dist <= threshold)
{
*out_dist = dist;
result->data.std.idx = 0;
result->data.std.t = 0;
result->idx = 0;
result->t = 0;
return TRUE;
}
@@ -437,7 +463,8 @@ gsk_standard_contour_get_closest_point (const GskContour *contour,
continue;
gsk_curve_init (&c, self->ops[i]);
if (gsk_curve_get_closest_point (&c, point, threshold, &distance, &t))
if (gsk_curve_get_closest_point (&c, point, threshold, &distance, &t) &&
distance < threshold)
{
best_idx = i;
best_t = t;
@@ -448,8 +475,8 @@ gsk_standard_contour_get_closest_point (const GskContour *contour,
if (best_idx != G_MAXUINT)
{
*out_dist = threshold;
result->data.std.idx = best_idx;
result->data.std.t = best_t;
result->idx = best_idx;
result->t = best_t;
return TRUE;
}
@@ -464,14 +491,14 @@ gsk_standard_contour_get_position (const GskContour *contour,
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
if (G_UNLIKELY (point->data.std.idx == 0))
if (G_UNLIKELY (point->idx == 0))
{
*position = self->points[0];
return;
}
gsk_curve_init (&curve, self->ops[point->data.std.idx]);
gsk_curve_get_point (&curve, point->data.std.t, position);
gsk_curve_init (&curve, self->ops[point->idx]);
gsk_curve_get_point (&curve, point->t, position);
}
static void
@@ -485,19 +512,19 @@ gsk_standard_contour_get_tangent (const GskContour *contour,
gsize idx;
float t;
if (G_UNLIKELY (point->data.std.idx == 0))
if (G_UNLIKELY (point->idx == 0))
{
graphene_vec2_init (tangent, 1, 0);
return;
}
idx = point->data.std.idx;
t = point->data.std.t;
idx = point->idx;
t = point->t;
if (t == 0 && direction == GSK_PATH_START)
{
/* Look at the previous segment */
if (idx > 0)
if (idx > 1)
{
idx--;
t = 1;
@@ -518,7 +545,7 @@ gsk_standard_contour_get_tangent (const GskContour *contour,
}
else if (self->flags & GSK_PATH_CLOSED)
{
idx = 0;
idx = 1;
t = 0;
}
}
@@ -535,11 +562,310 @@ gsk_standard_contour_get_curvature (const GskContour *contour,
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
if (G_UNLIKELY (point->data.std.idx == 0))
if (G_UNLIKELY (point->idx == 0))
return 0;
gsk_curve_init (&curve, self->ops[point->data.std.idx]);
return gsk_curve_get_curvature (&curve, point->data.std.t, center);
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);
}
}
typedef struct
{
float t;
float length;
} GskCurveLutEntry;
typedef struct
{
gsize op;
float length;
#ifdef USE_LUT
GArray *lut;
#endif
} GskCurveMeasureData;
typedef struct
{
float tolerance;
GArray *measure_data;
} GskStandardContourMeasureData;
#ifdef USE_LUT
static void
clear_lut (gpointer data)
{
GskCurveMeasureData *md = data;
g_clear_pointer (&md->lut, g_array_unref);
}
#endif
static gpointer
gsk_standard_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasureData *cd;
GskCurveMeasureData md;
cd = g_new0 (GskStandardContourMeasureData, 1);
cd->tolerance = tolerance;
cd->measure_data = g_array_new (FALSE, FALSE, sizeof (GskCurveMeasureData));
*out_length = 0;
md.op = 0;
md.length = 0;
#ifdef USE_LUT
g_array_set_clear_func (cd->measure_data, clear_lut);
md.lut = NULL;
#endif
g_array_append_val (cd->measure_data, md);
for (gsize i = 1; i < self->n_ops; i ++)
{
GskCurve curve;
gsk_curve_init (&curve, self->ops[i]);
md.op = i;
md.length = gsk_curve_get_length (&curve);
*out_length += md.length;
#ifdef USE_LUT
md.lut = g_array_new (FALSE, FALSE, sizeof (GskCurveLutEntry));
GskCurveLutEntry e;
gsize n;
if (curve.op == GSK_PATH_LINE ||
curve.op == GSK_PATH_CLOSE)
n = 0;
else
n = (int) ceilf (md.length / tolerance);
e.t = 0;
e.length = 0;
g_array_append_val (md.lut, e);
for (gsize j = 1; j < n; j++)
{
GskCurve curve1;
e.t = j / (float) n;
gsk_curve_split (&curve, e.t, &curve1, NULL);
e.length = gsk_curve_get_length (&curve1);
g_array_append_val (md.lut, e);
}
e.t = 1;
e.length = md.length;
g_array_append_val (md.lut, e);
g_array_append_val (cd->measure_data, md);
#endif
}
return cd;
}
static void
gsk_standard_contour_free_measure (const GskContour *contour,
gpointer data)
{
GskStandardContourMeasureData *cd = data;
g_array_unref (cd->measure_data);
g_free (cd);
}
static void
gsk_standard_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskRealPathPoint *result)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasureData *cd = measure_data;
unsigned int i;
GskCurveMeasureData *md;
for (i = 1; i < cd->measure_data->len; i++)
{
md = &g_array_index (cd->measure_data, GskCurveMeasureData, i);
if (distance < md->length)
break;
distance -= md->length;
}
if (i == self->n_ops)
{
result->idx = self->n_ops - 1;
result->t = 1;
}
else if (distance > 0)
{
result->idx = i;
#ifndef USE_LUT
GskCurve curve;
gsk_curve_init (&curve, self->ops[i]);
result->t = gsk_curve_at_length (&curve, distance, cd->tolerance / 2);
#else
GskCurveLutEntry *e0, *e1;
e1 = &g_array_index (md->lut, GskCurveLutEntry, 0);
for (gsize j = 1; j < md->lut->len; j++)
{
e0 = e1;
e1 = &g_array_index (md->lut, GskCurveLutEntry, j);
if (e1->length > distance)
{
float fraction = (e1->length - distance) / (e1->length - e0->length);
result->t = e0->t + fraction * (e1->t - e0->t);
break;
}
}
#endif
}
else
{
result->idx = i;
result->t = 0;
}
}
static float
gsk_standard_contour_get_distance (const GskContour *contour,
GskRealPathPoint *point,
gpointer measure_data)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasureData *cd = measure_data;
float length;
GskCurveMeasureData *md;
if (G_UNLIKELY (point->idx == 0))
return 0;
length = 0;
for (gsize i = 1; i < self->n_ops; i++)
{
md = &g_array_index (cd->measure_data, GskCurveMeasureData, i);
if (i == point->idx)
break;
length += md->length;
}
if (point->t == 1)
{
length += md->length;
}
else if (point->t > 0)
{
#ifndef USE_LUT
GskCurve curve, curve1;
gsk_curve_init (&curve, self->ops[md->op]);
gsk_curve_split (&curve, point->t, &curve1, NULL);
length += gsk_curve_get_length (&curve1);
#else
GskCurveLutEntry *e0, *e1;
e1 = &g_array_index (md->lut, GskCurveLutEntry, 0);
for (gsize i = 1; i < md->lut->len; i++)
{
e0 = e1;
e1 = &g_array_index (md->lut, GskCurveLutEntry, i);
if (e1->t > point->t)
{
float fraction = (point->t - e0->t) / (e1->t - e0->t);
length += e0->length + fraction * (e1->length - e0->length);
break;
}
}
#endif
}
return length;
}
static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
@@ -556,10 +882,16 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
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,
gsk_standard_contour_init_measure,
gsk_standard_contour_free_measure,
gsk_standard_contour_get_point,
gsk_standard_contour_get_distance,
};
/* You must ensure the contour has enough size allocated,
@@ -588,10 +920,8 @@ gsk_standard_contour_init (GskContour *contour,
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);
}
self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]),
gsk_pathop_points (ops[i]) + offset);
}
GskContour *
@@ -696,6 +1026,12 @@ gsk_contour_get_winding (const GskContour *self,
return self->klass->get_winding (self, point);
}
gsize
gsk_contour_get_n_points (const GskContour *self)
{
return self->klass->get_n_points (self);
}
gboolean
gsk_contour_get_closest_point (const GskContour *self,
const graphene_point_t *point,
@@ -731,6 +1067,48 @@ gsk_contour_get_curvature (const GskContour *self,
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);
}
gpointer
gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length)
{
return self->klass->init_measure (self, tolerance, out_length);
}
void
gsk_contour_free_measure (const GskContour *self,
gpointer data)
{
self->klass->free_measure (self, data);
}
void
gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float offset,
GskRealPathPoint *result)
{
self->klass->get_point (self, measure_data, offset, result);
}
float
gsk_contour_get_distance (const GskContour *self,
GskRealPathPoint *point,
gpointer measure_data)
{
return self->klass->get_distance (self, point, measure_data);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@@ -57,14 +57,17 @@ void gsk_contour_get_start_end (const GskContou
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,
@@ -73,4 +76,24 @@ float gsk_contour_get_curvature (const GskContou
GskRealPathPoint *point,
graphene_point_t *center);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gboolean emit_move_to,
GskRealPathPoint *start,
GskRealPathPoint *end);
gpointer gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float offset,
GskRealPathPoint *result);
float gsk_contour_get_distance (const GskContour *self,
GskRealPathPoint *point,
gpointer measure_data);
G_END_DECLS

View File

@@ -24,7 +24,7 @@
/* GskCurve collects all the functionality we need for Bézier segments */
#define MIN_PROGRESS (1/1024.f)
#define MIN_PROGRESS (1/2048.f)
typedef struct _GskCurveClass GskCurveClass;
@@ -524,20 +524,6 @@ gsk_quad_curve_segment (const GskCurve *curve,
gsk_quad_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_quad_curve_too_curvy (const GskQuadCurve *self,
float tolerance)
{
const graphene_point_t *pts = self->points;
float dx, dy;
dx = fabs (pts[1].x / 2 - (pts[0].x + pts[2].x) / 4);
dy = fabs (pts[1].y / 2 - (pts[0].y + pts[2].y) / 4);
return MAX (dx, dy) > tolerance;
}
static gboolean
gsk_quad_curve_decompose_step (const GskCurve *curve,
float start_progress,
@@ -550,8 +536,8 @@ gsk_quad_curve_decompose_step (const GskCurve *curve,
GskCurve left, right;
float mid_progress;
if (!gsk_quad_curve_too_curvy (self, tolerance))
return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (graphene_point_distance (&self->points[0], &self->points[2], NULL, NULL) < tolerance)
return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
@@ -994,25 +980,6 @@ gsk_cubic_curve_segment (const GskCurve *curve,
gsk_cubic_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_cubic_curve_too_curvy (const GskCubicCurve *self,
float tolerance)
{
const graphene_point_t *pts = self->points;
graphene_point_t p;
graphene_point_interpolate (&pts[0], &pts[3], 1.0f / 3, &p);
if (MAX (ABS (p.x - pts[1].x), ABS (p.y - pts[1].y)) > tolerance)
return TRUE;
graphene_point_interpolate (&pts[0], &pts[3], 2.0f / 3, &p);
if (MAX (ABS (p.x - pts[2].x), ABS (p.y - pts[2].y)) > tolerance)
return TRUE;
return FALSE;
}
static gboolean
gsk_cubic_curve_decompose_step (const GskCurve *curve,
float start_progress,
@@ -1025,8 +992,8 @@ gsk_cubic_curve_decompose_step (const GskCurve *curve,
GskCurve left, right;
float mid_progress;
if (!gsk_cubic_curve_too_curvy (self, tolerance))
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (graphene_point_distance (&self->points[0], &self->points[3], NULL, NULL) < tolerance)
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
@@ -1528,6 +1495,132 @@ gsk_curve_get_closest_point (const GskCurve *curve,
return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t);
}
/* Legendre-Gauss values for n=24,
* see https://pomax.github.io/bezierinfo/legendre-gauss.html
*/
const double T[] = {
-0.0640568928626056260850430826247450385909,
0.0640568928626056260850430826247450385909,
-0.1911188674736163091586398207570696318404,
0.1911188674736163091586398207570696318404,
-0.3150426796961633743867932913198102407864,
0.3150426796961633743867932913198102407864,
-0.4337935076260451384870842319133497124524,
0.4337935076260451384870842319133497124524,
-0.5454214713888395356583756172183723700107,
0.5454214713888395356583756172183723700107,
-0.6480936519369755692524957869107476266696,
0.6480936519369755692524957869107476266696,
-0.7401241915785543642438281030999784255232,
0.7401241915785543642438281030999784255232,
-0.8200019859739029219539498726697452080761,
0.8200019859739029219539498726697452080761,
-0.8864155270044010342131543419821967550873,
0.8864155270044010342131543419821967550873,
-0.9382745520027327585236490017087214496548,
0.9382745520027327585236490017087214496548,
-0.9747285559713094981983919930081690617411,
0.9747285559713094981983919930081690617411,
-0.9951872199970213601799974097007368118745,
0.9951872199970213601799974097007368118745,
};
const double C[] = {
0.1279381953467521569740561652246953718517,
0.1279381953467521569740561652246953718517,
0.1258374563468282961213753825111836887264,
0.1258374563468282961213753825111836887264,
0.121670472927803391204463153476262425607,
0.121670472927803391204463153476262425607,
0.1155056680537256013533444839067835598622,
0.1155056680537256013533444839067835598622,
0.1074442701159656347825773424466062227946,
0.1074442701159656347825773424466062227946,
0.0976186521041138882698806644642471544279,
0.0976186521041138882698806644642471544279,
0.086190161531953275917185202983742667185,
0.086190161531953275917185202983742667185,
0.0733464814110803057340336152531165181193,
0.0733464814110803057340336152531165181193,
0.0592985849154367807463677585001085845412,
0.0592985849154367807463677585001085845412,
0.0442774388174198061686027482113382288593,
0.0442774388174198061686027482113382288593,
0.0285313886289336631813078159518782864491,
0.0285313886289336631813078159518782864491,
0.0123412297999871995468056670700372915759,
0.0123412297999871995468056670700372915759,
};
/* Compute arclength by using Gauss quadrature on
*
* \int_0^z \sqrt{ (dx/dt)^2 + (dy/dt)^2 } dt
*/
float
gsk_curve_get_length (const GskCurve *curve)
{
double z = 0.5;
double sum;
GskCurve derivative;
graphene_point_t d;
if (curve->op == GSK_PATH_LINE ||
curve->op == GSK_PATH_CLOSE)
return graphene_point_distance (gsk_curve_get_start_point (curve),
gsk_curve_get_end_point (curve),
NULL, NULL);
gsk_curve_get_derivative (curve, &derivative);
sum = 0;
for (unsigned int i = 0; i < G_N_ELEMENTS (T); i++)
{
double t = z * T[i] + z;
gsk_curve_get_point (&derivative, t, &d);
sum += C[i] * sqrt (d.x * d.x + d.y * d.y);
}
return z * sum;
}
/* Compute the inverse of the arclength using bisection,
* to a given precision
*/
float
gsk_curve_at_length (const GskCurve *curve,
float length,
float epsilon)
{
float t1, t2, t, l;
GskCurve c1;
//int loop_count = 0;
t1 = 0;
t2 = 1;
while (t1 < t2)
{
//loop_count++;
t = (t1 + t2) / 2;
gsk_curve_split (curve, t, &c1, NULL);
l = gsk_curve_get_length (&c1);
if (fabs (length - l) < epsilon)
break;
else if (l < length)
t1 = t;
else
t2 = t;
}
//g_print ("loop count %d\n", loop_count);
return t;
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@@ -153,5 +153,10 @@ gboolean gsk_curve_get_closest_point (const GskCurve
float *out_dist,
float *out_t);
float gsk_curve_get_length (const GskCurve *curve);
float gsk_curve_at_length (const GskCurve *curve,
float distance,
float epsilon);
G_END_DECLS

View File

@@ -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
@@ -42,8 +42,6 @@
* @GSK_REPEAT_NODE: A node that repeats the child's contents
* @GSK_CLIP_NODE: A node that clips its child to a rectangular area
* @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle
* @GSK_FILL_NODE: A node that fills a path
* @GSK_STROKE_NODE: A node that strokes a path
* @GSK_SHADOW_NODE: A node that draws a shadow below its child
* @GSK_BLEND_NODE: A node that blends two children together
* @GSK_CROSS_FADE_NODE: A node that cross-fades between two children
@@ -53,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 {
@@ -76,8 +76,6 @@ typedef enum {
GSK_REPEAT_NODE,
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE,
GSK_SHADOW_NODE,
GSK_BLEND_NODE,
GSK_CROSS_FADE_NODE,
@@ -86,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;
/**

View File

@@ -42,6 +42,8 @@
* Conceptually, a path consists of zero or more contours (continous, connected
* curves), each of which may or may not be closed. Contours are typically
* constructed from Bézier segments.
*
* Since: 4.14
*/
struct _GskPath
@@ -420,6 +422,7 @@ gsk_path_get_bounds (GskPath *self,
*
* Returns: `TRUE` if the path has bounds, `FALSE` if the path is known
* to be empty and have no bounds.
* Since: 4.14
*/
gboolean
gsk_path_get_stroke_bounds (GskPath *self,
@@ -490,6 +493,72 @@ gsk_path_in_fill (GskPath *self,
}
}
/**
* gsk_path_get_start_point:
* @self: a `GskPath`
* @result: (out caller-allocates): return location for point
*
* Gets the start point of the path.
*
* An empty path has no points, so `FALSE`
* is returned in this case.
*
* Returns: `TRUE` if @result was filled
*
* Since: 4.14
*/
gboolean
gsk_path_get_start_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
res->contour = 0;
res->idx = 1;
res->t = 0;
return TRUE;
}
/**
* gsk_path_get_end_point:
* @self: a `GskPath`
* @result: (out caller-allocates): return location for point
*
* Gets the end point of the path.
*
* An empty path has no points, so `FALSE`
* is returned in this case.
*
* Returns: `TRUE` if @result was filled
*
* Since: 4.14
*/
gboolean
gsk_path_get_end_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
res->contour = self->n_contours - 1;
res->idx = gsk_contour_get_n_points (self->contours[self->n_contours - 1]) - 1;
res->t = 1;
return TRUE;
}
/**
* gsk_path_get_closest_point:
* @self: a `GskPath`
@@ -532,7 +601,6 @@ gsk_path_get_closest_point (GskPath *self,
{
found = TRUE;
res->contour = i;
res->path = self;
threshold = distance;
}
}

View File

@@ -41,6 +41,8 @@ G_BEGIN_DECLS
* 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
{
@@ -110,6 +112,13 @@ gboolean gsk_path_in_fill (GskPath
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,

View File

@@ -63,6 +63,8 @@
* back with a line to the starting point.
*
* This is similar for how paths are drawn in Cairo.
*
* Since: 4.14
*/
struct _GskPathBuilder
@@ -983,3 +985,77 @@ gsk_path_builder_add_layout (GskPathBuilder *self,
cairo_destroy (cr);
cairo_surface_destroy (surface);
}
/**
* gsk_path_builder_add_segment:
* @self: a `GskPathBuilder`
* @path: the `GskPath` to take the segment to
* @start: the point on @path to start at
* @end: the point on @path to end at
*
* Adds to @self the segment of @path from @start to @end.
*
* If @start is equal to or after @end, the path will first add the
* segment from @start to the end of the path, and then add the segment
* from the beginning to @end. If the path is closed, these segments
* will be connected.
*
* Note that this method always adds a path with the given start point
* and end point. To add a closed path, use [method@Gsk.PathBuilder.add_path].
*
* Since: 4.14
*/
void
gsk_path_builder_add_segment (GskPathBuilder *self,
GskPath *path,
const GskPathPoint *start,
const GskPathPoint *end)
{
GskRealPathPoint *s = (GskRealPathPoint *) start;
GskRealPathPoint *e = (GskRealPathPoint *) end;
const GskContour *contour;
gsize n_contours = gsk_path_get_n_contours (path);
g_return_if_fail (self != NULL);
g_return_if_fail (path != NULL);
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
g_return_if_fail (s->contour < n_contours);
g_return_if_fail (e->contour < n_contours);
contour = gsk_path_get_contour (path, s->contour);
if (s->contour == e->contour)
{
if (gsk_path_point_compare (start, end) < 0)
{
gsk_contour_add_segment (contour, self, TRUE, s, e);
goto out;
}
else if (n_contours == 1)
{
gsk_contour_add_segment (contour, self, TRUE,
s,
&(GskRealPathPoint) { s->contour, gsk_contour_get_n_points (contour) - 1, 1 });
gsk_contour_add_segment (contour, self, FALSE,
&(GskRealPathPoint) { s->contour, 1, 0 },
e);
goto out;
}
}
gsk_contour_add_segment (contour, self, TRUE,
s,
&(GskRealPathPoint) { s->contour, gsk_contour_get_n_points (contour) - 1, 1 });
for (gsize i = (s->contour + 1) % n_contours; i != e->contour; i = (i + 1) % n_contours)
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (path, i)));
contour = gsk_path_get_contour (path, e->contour);
gsk_contour_add_segment (contour, self, FALSE,
&(GskRealPathPoint) { e->contour, 1, 0 },
e);
out:
gsk_path_builder_end_current (self);
}

View File

@@ -68,6 +68,11 @@ 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,

328
gsk/gskpathmeasure.c Normal file
View File

@@ -0,0 +1,328 @@
/*
* 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 "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathpointprivate.h"
#include "gskpathprivate.h"
/**
* GskPathMeasure:
*
* `GskPathMeasure` is an object that allows measurements
* on `GskPath`s such as determining the length of the path.
*
* Many measuring operations require approximating the path
* with simpler shapes. Therefore, a `GskPathMeasure` has
* a tolerance that determines what precision is required
* for such approximations.
*
* A `GskPathMeasure` struct is a reference counted struct
* and should be treated as opaque.
*/
typedef struct _GskContourMeasure GskContourMeasure;
struct _GskContourMeasure
{
float length;
gpointer contour_data;
};
struct _GskPathMeasure
{
/*< private >*/
guint ref_count;
GskPath *path;
float tolerance;
float length;
gsize n_contours;
GskContourMeasure measures[];
};
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
gsk_path_measure_ref,
gsk_path_measure_unref)
/**
* gsk_path_measure_new:
* @path: the path to measure
*
* Creates a measure object for the given @path.
*
* Returns: a new `GskPathMeasure` representing @path
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_new (GskPath *path)
{
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
}
/**
* gsk_path_measure_new_with_tolerance:
* @path: the path to measure
* @tolerance: the tolerance for measuring operations
*
* Creates a measure object for the given @path and @tolerance.
*
* Returns: a new `GskPathMeasure` representing @path
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance)
{
GskPathMeasure *self;
gsize i, n_contours;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (tolerance > 0, NULL);
n_contours = gsk_path_get_n_contours (path);
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
self->ref_count = 1;
self->path = gsk_path_ref (path);
self->tolerance = tolerance;
self->n_contours = n_contours;
for (i = 0; i < n_contours; i++)
{
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
self->tolerance,
&self->measures[i].length);
self->length += self->measures[i].length;
}
return self;
}
/**
* gsk_path_measure_ref:
* @self: a `GskPathMeasure`
*
* Increases the reference count of a `GskPathMeasure` by one.
*
* Returns: the passed in `GskPathMeasure`.
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_ref (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_measure_unref:
* @self: a `GskPathMeasure`
*
* Decreases the reference count of a `GskPathMeasure` by one.
*
* If the resulting reference count is zero, frees the object.
*
* Since: 4.14
*/
void
gsk_path_measure_unref (GskPathMeasure *self)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
for (i = 0; i < self->n_contours; i++)
{
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data);
}
gsk_path_unref (self->path);
g_free (self);
}
/**
* gsk_path_measure_get_path:
* @self: a `GskPathMeasure`
*
* Returns the path that the measure was created for.
*
* Returns: (transfer none): the path of @self
*
* Since: 4.14
*/
GskPath *
gsk_path_measure_get_path (GskPathMeasure *self)
{
return self->path;
}
/**
* gsk_path_measure_get_tolerance:
* @self: a `GskPathMeasure`
*
* Returns the tolerance that the measure was created with.
*
* Returns: the tolerance of @self
*
* Since: 4.14
*/
float
gsk_path_measure_get_tolerance (GskPathMeasure *self)
{
return self->tolerance;
}
/**
* gsk_path_measure_get_length:
* @self: a `GskPathMeasure`
*
* Gets the length of the path being measured.
*
* The length is cached, so this function does not do any work.
*
* Returns: The length of the path measured by @self
*
* Since: 4.14
*/
float
gsk_path_measure_get_length (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->length;
}
static float
gsk_path_measure_clamp_distance (GskPathMeasure *self,
float distance)
{
if (isnan (distance))
return 0;
return CLAMP (distance, 0, self->length);
}
/**
* gsk_path_measure_get_point:
* @self: a `GskPathMeasure`
* @distance: the distance
* @result: (out caller-allocates): return location for the result
*
* Sets @result to the point at the given distance into the path.
*
* An empty path has no points, so `FALSE` is returned in that case.
*
* Returns: `TRUE` if @result was set
*
* Since: 4.14
*/
gboolean
gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
gsize i;
float offset;
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
offset = gsk_path_measure_clamp_distance (self, distance);
for (i = 0; i < self->n_contours - 1; i++)
{
if (offset < self->measures[i].length)
break;
offset -= self->measures[i].length;
}
g_assert (0 <= i && i < self->n_contours);
offset = CLAMP (offset, 0, self->measures[i].length);
contour = gsk_path_get_contour (self->path, i);
gsk_contour_get_point (contour, self->measures[i].contour_data, offset, res);
res->contour = i;
return TRUE;
}
/**
* gsk_path_point_get_distance:
* @measure: a `GskPathMeasure`
* @point: a `GskPathPoint on the path of @self
*
* Returns the distance from the beginning of the path
* to @point.
*
* Returns: the distance of @point
*
* Since: 4.14
*/
float
gsk_path_point_get_distance (GskPathMeasure *measure,
const GskPathPoint *point)
{
GskRealPathPoint *p = (GskRealPathPoint *)point;
const GskContour *contour;
float contour_offset = 0;
g_return_val_if_fail (measure != NULL, 0);
g_return_val_if_fail (point != NULL, 0);
g_return_val_if_fail (p->contour < measure->n_contours, 0);
contour = gsk_path_get_contour (measure->path, p->contour);
for (gsize i = 0; i < measure->n_contours; i++)
{
if (contour == gsk_path_get_contour (measure->path, i))
return contour_offset + gsk_contour_get_distance (contour,
p,
measure->measures[i].contour_data);
contour_offset += measure->measures[i].length;
}
g_return_val_if_reached (0);
}

62
gsk/gskpathmeasure.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* 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/gskpath.h>
#include <gsk/gskpathpoint.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
GDK_AVAILABLE_IN_4_14
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_new (GskPath *path);
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance);
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
void gsk_path_measure_unref (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_length (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
GskPathPoint *result);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
G_END_DECLS

View File

@@ -22,6 +22,7 @@
#include "gskpathpointprivate.h"
#include "gskcontourprivate.h"
#include "gskpathmeasure.h"
#include "gdk/gdkprivate.h"
@@ -33,12 +34,15 @@
* 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].
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point]
* or [method@Gsk.PathMeasure.get_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,
@@ -64,6 +68,77 @@ 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`
@@ -81,11 +156,14 @@ gsk_path_point_get_position (GskPath *path,
graphene_point_t *position)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
const GskContour *contour;
g_return_if_fail (path == self->path);
g_return_if_fail (contour != NULL);
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);
}
@@ -114,11 +192,14 @@ gsk_path_point_get_tangent (GskPath *path,
graphene_vec2_t *tangent)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
const GskContour *contour;
g_return_if_fail (path == self->path);
g_return_if_fail (contour != NULL);
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);
}
@@ -126,7 +207,7 @@ gsk_path_point_get_tangent (GskPath *path,
* gsk_path_point_get_curvature:
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @center: (out caller-allocates): Return location for
* @center: (out caller-allocates) (nullable): Return location for
* the center of the osculating circle
*
* Calculates the curvature of the path at the point.
@@ -146,10 +227,12 @@ gsk_path_point_get_curvature (GskPath *path,
graphene_point_t *center)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
const GskContour *contour;
g_return_val_if_fail (path == self->path, 0);
g_return_val_if_fail (contour != NULL, 0);
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);
}

View File

@@ -48,6 +48,14 @@ GskPathPoint * gsk_path_point_copy (GskPathPoint *poin
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,
@@ -64,4 +72,8 @@ float gsk_path_point_get_curvature (GskPath *path
const GskPathPoint *point,
graphene_point_t *center);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_distance (GskPathMeasure *measure,
const GskPathPoint *point);
G_END_DECLS

View File

@@ -7,15 +7,9 @@ G_BEGIN_DECLS
struct _GskRealPathPoint
{
GskPath *path;
gsize contour;
union {
struct {
unsigned int idx;
float t;
} std;
} data;
gsize idx;
float t;
};
G_STATIC_ASSERT (sizeof (GskRealPathPoint) <= sizeof (GskPathPoint));

View File

@@ -3970,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);
@@ -4141,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)
@@ -4372,6 +4372,15 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
/* }}} */
/* {{{ GSK_FILL_NODE */
/**
* GskFillNode:
*
* A render node filling the child node in the area given by [struct@Gsk.Path]
* and [enum@Gsk.FillRule].
*
* Since: 4.14
*/
struct _GskFillNode
{
GskRenderNode render_node;
@@ -4572,6 +4581,15 @@ gsk_fill_node_get_fill_rule (const GskRenderNode *node)
/* }}} */
/* {{{ GSK_STROKE_NODE */
/**
* GskStrokeNode:
*
* A render node that will stroke the child node along the given [struct@Gsk.Path]
* using the [struct@Gsk.Stroke] attributes.
*
* Since: 4.14
*/
struct _GskStrokeNode
{
GskRenderNode render_node;

View File

@@ -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[];

View File

@@ -26,6 +26,8 @@
*
* 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)

View File

@@ -27,6 +27,7 @@
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskPathPoint GskPathPoint;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskStroke GskStroke;

View File

@@ -28,6 +28,7 @@ gsk_public_sources = files([
'gskglshader.c',
'gskpath.c',
'gskpathbuilder.c',
'gskpathmeasure.c',
'gskpathpoint.c',
'gskrenderer.c',
'gskrendernode.c',
@@ -75,6 +76,7 @@ gsk_public_headers = files([
'gskglshader.h',
'gskpath.h',
'gskpathbuilder.h',
'gskpathmeasure.h',
'gskpathpoint.h',
'gskrenderer.h',
'gskrendernode.h',

View File

@@ -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
*/

View File

@@ -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]

View File

@@ -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.
*/

View File

@@ -23,7 +23,7 @@
#include <glib-object.h>
#include <gtk/gtk.h>
#include "gtkprintbackendprivate.h"
#include "gtk/print/gtkprintbackendprivate.h"
G_BEGIN_DECLS

View File

@@ -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__ */

2091
po/es.po

File diff suppressed because it is too large Load Diff

1754
po/gl.po

File diff suppressed because it is too large Load Diff

View File

@@ -145,6 +145,28 @@ test_curve_crossing (void)
}
}
static void
test_bad_split (void)
{
GskCurve c, c1, c2;
float t[2], l, l1, l2;
parse_curve (&c, "M 0 0 C 2 0 4 0 4 0");
l = gsk_curve_get_length (&c);
t[0] = 0.5;
t[1] = gsk_curve_at_length (&c, 2, FLT_EPSILON);
for (unsigned int i = 0; i < G_N_ELEMENTS (t); i++)
{
gsk_curve_split (&c, t[i], &c1, &c2);
l1 = gsk_curve_get_length (&c1);
l2 = gsk_curve_get_length (&c2);
g_print ("split at t=%g: %g == %g + %g, error %f\n", t[i], l, l1, l2, l - (l1 + l2));
}
}
int
main (int argc,
char *argv[])
@@ -154,6 +176,7 @@ main (int argc,
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);
g_test_add_func ("/curve/special/bad-split", test_bad_split);
return g_test_run ();
}

View File

@@ -281,16 +281,29 @@ test_curve_decompose_into_cubic (void)
static void
test_curve_split (void)
{
for (int i = 0; i < 100; i++)
for (int i = 0; i < 1000; i++)
{
GskCurve c;
GskCurve c1, c2;
graphene_point_t p;
graphene_vec2_t t, t1, t2;
float tt;
float l, l1, l2;
init_random_curve (&c);
gsk_curve_split (&c, 0.5, &c1, &c2);
tt = g_test_rand_double_range (0, 1);
if (tt == 0 || tt == 1)
continue;
if (g_test_verbose ())
g_print ("curve %s\nt %g\n", gsk_curve_to_string (&c), tt);
gsk_curve_split (&c, tt, &c1, &c2);
if (g_test_verbose ())
g_print ("c1 %s\nc2 %s\n", gsk_curve_to_string (&c1), gsk_curve_to_string (&c2));
g_assert_true (c1.op == c.op);
g_assert_true (c2.op == c.op);
@@ -301,8 +314,9 @@ test_curve_split (void)
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);
gsk_curve_get_point (&c, tt, &p);
gsk_curve_get_tangent (&c, tt, &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));
@@ -317,6 +331,14 @@ test_curve_split (void)
gsk_curve_get_end_tangent (&c, &t1);
gsk_curve_get_end_tangent (&c2, &t2);
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
l = gsk_curve_get_length (&c);
l1 = gsk_curve_get_length (&c1);
l2 = gsk_curve_get_length (&c2);
g_print ("l %g l1 %g l2 %g\n", l, l1, l2);
g_assert_cmpfloat_with_epsilon (l, l1 + l2, 0.1);
}
}

View File

@@ -0,0 +1,272 @@
/*
* 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 void
test_basics (void)
{
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point, point1;
float epsilon = 0.001;
float length;
path = gsk_path_parse ("M 0 0 L 100 0 C 100 100 200 100 200 0");
measure = gsk_path_measure_new (path);
gsk_path_measure_get_point (measure, 0, &point);
gsk_path_get_start_point (path, &point1);
g_assert_true (gsk_path_point_equal (&point, &point1));
g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 0, epsilon);
gsk_path_measure_get_point (measure, 50, &point);
g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 50, epsilon);
gsk_path_measure_get_point (measure, 100, &point);
g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 100, epsilon);
gsk_path_measure_get_point (measure, 150, &point);
g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), 150, epsilon);
length = gsk_path_measure_get_length (measure);
gsk_path_measure_get_point (measure, length, &point);
gsk_path_get_end_point (path, &point1);
g_assert_true (gsk_path_point_equal (&point, &point1));
g_assert_cmpfloat_with_epsilon (gsk_path_point_get_distance (measure, &point), length, epsilon);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_bad_split (void)
{
GskPath *path, *path1;
GskPathMeasure *measure, *measure1;
GskPathBuilder *builder;
float split, length, epsilon;
GskPathPoint point1, point2;
/* An example that was isolated from the /path/segment test path.c
* It shows how uneven parametrization of cubics can lead to bad
* lengths reported by the measure.
*/
path = gsk_path_parse ("M 0 0 C 2 0 4 0 4 0");
measure = gsk_path_measure_new (path);
split = 2.962588;
length = gsk_path_measure_get_length (measure);
epsilon = MAX (length / 256, 1.f / 1024);
gsk_path_get_start_point (path, &point1);
gsk_path_measure_get_point (measure, split, &point2);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point1, &point2);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
g_assert_cmpfloat_with_epsilon (split, gsk_path_measure_get_length (measure1), epsilon);
gsk_path_measure_unref (measure1);
gsk_path_unref (path1);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_rect (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
graphene_point_t p;
graphene_vec2_t tangent, expected_tangent;
float length;
gboolean ret;
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
#define TEST_POS_AT(distance, X, Y) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
g_assert_true (ret); \
if (distance < length) \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - distance) < 0.01); \
else \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point)) < 0.01); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_tangent (path, &point, GSK_PATH_START, &tangent); \
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x1, y1), 0.01)); \
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent); \
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \
#define TEST_POS_AT2(distance, X, Y, expected_distance) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
g_assert_true (ret); \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - expected_distance) < 0.01); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
TEST_POS_AT (0, 0, 0)
TEST_POS_AT (25, 25, 0)
TEST_POS_AT (100, 100, 0)
TEST_POS_AT (110, 100, 10)
TEST_POS_AT (150, 100, 50)
TEST_POS_AT (175, 75, 50)
TEST_POS_AT (250, 0, 50)
TEST_POS_AT (260, 0, 40)
TEST_POS_AT2 (300, 0, 0, 0)
TEST_TANGENT_AT (0, 0, -1, 1, 0)
TEST_TANGENT_AT (50, 1, 0, 1, 0)
TEST_TANGENT_AT (100, 1, 0, 0, 1)
TEST_TANGENT_AT (125, 0, 1, 0, 1)
TEST_TANGENT_AT (150, 0, 1, -1, 0)
TEST_TANGENT_AT (200, -1, 0, -1, 0)
TEST_TANGENT_AT (250, -1, 0, 0, -1)
TEST_TANGENT_AT (275, 0, -1, 0, -1)
gsk_path_measure_unref (measure);
gsk_path_unref (path);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 50, -100, -50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
TEST_POS_AT (0, 100, 50)
TEST_POS_AT (25, 75, 50)
TEST_POS_AT (100, 0, 50)
TEST_POS_AT (110, 0, 40)
TEST_POS_AT (150, 0, 0)
TEST_POS_AT (175, 25, 0)
TEST_POS_AT (250, 100, 0)
TEST_POS_AT (260, 100, 10)
TEST_POS_AT (300, 100, 50)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
TEST_POS_AT (0, 100, 0)
TEST_POS_AT (25, 75, 0)
TEST_POS_AT (100, 0, 0)
TEST_POS_AT (110, 0, 10)
TEST_POS_AT (150, 0, 50)
TEST_POS_AT (175, 25, 50)
TEST_POS_AT (250, 100, 50)
TEST_POS_AT (260, 100, 40)
TEST_POS_AT (300, 100, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 0));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
TEST_POS_AT2 (0, 0, 0, 0)
TEST_POS_AT2 (25, 25, 0, 25)
TEST_POS_AT2 (100, 100, 0, 100)
TEST_POS_AT2 (110, 90, 0, 90)
TEST_POS_AT2 (200, 0, 0, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 0));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
/* These cases are ambiguous */
TEST_POS_AT2 (0, 100, 0, 0)
TEST_POS_AT2 (25, 75, 0, 25)
TEST_POS_AT2 (100, 0, 0, 100)
TEST_POS_AT2 (110, 10, 0, 110)
TEST_POS_AT2 (200, 100, 0, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 100, 0, -100));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
/* These cases are ambiguous */
TEST_POS_AT2 (0, 0, 100, 0)
TEST_POS_AT2 (25, 0, 75, 25)
TEST_POS_AT2 (100, 0, 0, 100)
TEST_POS_AT2 (110, 0, 10, 110)
TEST_POS_AT2 (200, 0, 100, 0)
#undef TEST_POS_AT
#undef TEST_POS_AT2
#undef TEST_TANGENT_AT
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/measure/basics", test_basics);
g_test_add_func ("/measure/bad-split", test_bad_split);
g_test_add_func ("/measure/rect", test_rect);
return g_test_run ();
}

555
testsuite/gsk/measure.c Normal file
View File

@@ -0,0 +1,555 @@
/*
* 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 curve 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);
}
static void
test_roundtrip (void)
{
GskPath *path;
GskPathMeasure *measure;
float length, seg_length;
int i;
GskPathPoint point;
float epsilon = 0.5;
path = create_random_path (10);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
for (i = 0; i <= 100; i++)
{
seg_length = length * i / 100.0f;
gsk_path_measure_get_point (measure, seg_length, &point);
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_point_get_distance (measure, &point), epsilon);
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_get_point (void)
{
static const guint max_contours = 5;
static const float tolerance = 1.0;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
guint n_discontinuities;
float length, offset, last_offset;
graphene_point_t p, last_point;
guint i, j;
gboolean ret;
for (i = 0; i < 10; i++)
{
path = create_random_path (max_contours);
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
length = gsk_path_measure_get_length (measure);
n_discontinuities = 0;
ret = gsk_path_measure_get_point (measure, 0, &point);
if (!ret)
{
g_assert_true (gsk_path_is_empty (path));
continue;
}
gsk_path_point_get_position (path, &point, &last_point);
/* FIXME: anything we can test with tangents here? */
last_offset = 0;
for (j = 1; j <= 1024; j++)
{
offset = length * j / 1024;
ret = gsk_path_measure_get_point (measure, offset, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
{
n_discontinuities++;
g_assert_cmpint (n_discontinuities, <, max_contours);
}
last_offset = offset;
last_point = p;
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
static void
test_closest_point (void)
{
static const float tolerance = 0.5;
GskPath *path, *path1, *path2;
GskPathMeasure *measure, *measure1, *measure2;
GskPathBuilder *builder;
GskPathPoint point;
guint i, j;
gboolean ret;
if (!g_test_slow ())
{
g_test_skip ("Skipping slow test");
return;
}
for (i = 0; i < 10; i++)
{
path1 = create_random_path (G_MAXUINT);
measure1 = gsk_path_measure_new_with_tolerance (path1, tolerance);
path2 = create_random_path (G_MAXUINT);
measure2 = gsk_path_measure_new_with_tolerance (path2, tolerance);
builder = gsk_path_builder_new ();
gsk_path_builder_add_path (builder, path1);
gsk_path_builder_add_path (builder, path2);
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
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));
graphene_point_t p1, p2, p;
graphene_vec2_t t1, t2, t;
float offset1, offset2, offset;
float distance1, distance2, distance;
offset1 = offset2 = offset = 0;
distance1 = distance2 = distance = 0;
ret = gsk_path_get_closest_point (path1, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path1, &point, &p1);
gsk_path_point_get_tangent (path1, &point, GSK_PATH_END, &t1);
offset1 = gsk_path_point_get_distance (measure1, &point);
distance1 = graphene_point_distance (&p1, &test, NULL, NULL);
ret = gsk_path_get_closest_point (path2, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path2, &point, &p2);
gsk_path_point_get_tangent (path2, &point, GSK_PATH_END, &t2);
offset2 = gsk_path_point_get_distance (measure2, &point);
distance2 = graphene_point_distance (&p2, &test, NULL, NULL);
ret = gsk_path_get_closest_point (path, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t);
offset = gsk_path_point_get_distance (measure, &point);
distance = graphene_point_distance (&p, &test, NULL, NULL);
if (distance1 == distance)
{
g_assert_cmpfloat (distance1, ==, distance);
g_assert_cmpfloat (p1.x, ==, p.x);
g_assert_cmpfloat (p1.y, ==, p.y);
g_assert_cmpfloat (offset1, ==, offset);
g_assert_true (graphene_vec2_equal (&t1, &t));
}
else
{
g_assert_cmpfloat (distance2, ==, distance);
g_assert_cmpfloat (p2.x, ==, p.x);
g_assert_cmpfloat (p2.y, ==, p.y);
g_assert_cmpfloat_with_epsilon (offset2 + gsk_path_measure_get_length (measure1), offset, MAX (G_MINFLOAT, offset / 1024));
g_assert_true (graphene_vec2_equal (&t2, &t));
}
}
gsk_path_measure_unref (measure2);
gsk_path_measure_unref (measure1);
gsk_path_measure_unref (measure);
gsk_path_unref (path2);
gsk_path_unref (path1);
gsk_path_unref (path);
}
}
static void
test_closest_point_for_point (void)
{
static const float tolerance = 0.5;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
float length, offset, distance;
graphene_point_t p, closest_point;
guint i, j;
gboolean ret;
if (!g_test_slow ())
{
g_test_skip ("Skipping slow test");
return;
}
for (i = 0; i < 100; i++)
{
path = create_random_path (G_MAXUINT);
if (gsk_path_is_empty (path))
{
/* empty paths have no closest point to anything */
gsk_path_unref (path);
continue;
}
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
length = gsk_path_measure_get_length (measure);
for (j = 0; j < 100; j++)
{
offset = g_test_rand_double_range (0, length);
ret = gsk_path_measure_get_point (measure, offset, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
ret = gsk_path_get_closest_point (path, &p, 2 * tolerance, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &closest_point);
//closest_offset = gsk_path_point_get_distance (measure, &point);
distance = graphene_point_distance (&p, &closest_point, NULL, NULL);
/* should be given due to the TRUE return above, but who knows... */
g_assert_cmpfloat (distance, <=, 2 * tolerance);
g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, 2 * tolerance);
/* we can't check offsets here, since we might hit self-intersections
g_assert_cmpfloat_with_epsilon (closest_offset, offset, 2 * tolerance);
*/
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/measure/roundtrip", test_roundtrip);
g_test_add_func ("/measure/get_point", test_get_point);
g_test_add_func ("/measure/closest_point", test_closest_point);
g_test_add_func ("/measure/closest_point_for_point", test_closest_point_for_point);
return g_test_run ();
}

View File

@@ -371,6 +371,8 @@ tests = [
['shader'],
['path'],
['path-special-cases'],
['measure'],
['measure-special-cases'],
]
test_cargs = []

View File

@@ -443,7 +443,6 @@ test_path_point (void)
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t2);
curvature = gsk_path_point_get_curvature (path, &point, &center);
g_print ("%g %g\n", graphene_vec2_get_x (&t1), graphene_vec2_get_y (&t1));
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);
@@ -453,6 +452,106 @@ test_path_point (void)
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);
}
int
main (int argc, char *argv[])
{
@@ -463,6 +562,9 @@ main (int argc, char *argv[])
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 ("/measure/bad-in-fill", test_bad_in_fill);
g_test_add_func ("/measure/unclosed-in-fill", test_unclosed_in_fill);
return g_test_run ();
}

View File

@@ -582,6 +582,166 @@ test_parse (void)
}
}
#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[])
@@ -590,6 +750,8 @@ main (int argc,
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 ();
}

View File

@@ -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);