Compare commits

...

31 Commits

Author SHA1 Message Date
Matthias Clasen
24750b6e62 gsk: Make glyphy optional for now
Enable glyphy by default, but make it possible
to get the previous cairo rendering by setting
GSK_DEBUG=no-glyphy in the environment.

Keeping this optional will make it easier to
debug issues and compare output.
2023-09-23 20:16:12 -04:00
Matthias Clasen
7643791064 Add an animated text example 2023-09-23 20:16:12 -04:00
Matthias Clasen
d8861f403c Add a font rendering test 2023-09-23 20:16:12 -04:00
Matthias Clasen
aacd4840dd glyphy: Implement synthetic italic
Use the font matrix found in the FcPattern to
transform the quads we use to render the glyphs.

This makes Cantarell Italic come out just like
it does with freetype.
2023-09-23 20:16:12 -04:00
Matthias Clasen
070e9a3085 glyphy: Update for api changes in glyphy
With this, synthetic bold fonts work as well
as they do with freetype.
2023-09-23 20:16:12 -04:00
Matthias Clasen
6080a55453 glyphy: Implement synthetic bold
When the font appears to be synthetic bold (as
indicated by th embolden property in FcPattern),
use glyphys boldness uniform to render the font
bolder.
2023-09-23 20:16:12 -04:00
Matthias Clasen
752cb95a59 glyphy: Remove glyphy debug code
We don't have a knob to turn this on, and it is
not really useful outside of glyphy itself, so
remove it.
2023-09-23 20:16:12 -04:00
Matthias Clasen
d8eb91c7e9 glyphy: Simplify the path
Use path ops to simplify the path we get from
harfbuzz, since variable fonts often have overlapping
contours, and the glyphy shader can't handle those.
2023-09-23 20:16:12 -04:00
Matthias Clasen
fbeb3fa75e build: Bump the harfbuzz requirement to 4.0
The hb_font_get_glyph_shape api was introduced
in 4.0.
2023-09-23 20:16:12 -04:00
Matthias Clasen
7fb1a8bd2c glyphy: Pencil in outline rendering 2023-09-23 20:16:12 -04:00
Matthias Clasen
bb8730bf59 glyphy: Declare that we are using dFdx
GLES2 does not have these by default :(
2023-09-23 20:16:12 -04:00
Christian Hergert
4782dfebe3 gsk/gl: Fix output color of glyphy fragment shader
This copied more or less what the coloring vertex shader
was doing in that we premultiply alpha. That changes how
we apply alpha in the fragment shader to match.

This fixes a white halo around the fonts.
2023-09-23 20:16:12 -04:00
Matthias Clasen
cd131081cf glyphy: Make glyphy cache size-independent
Use a hb font at nominal size when generating sdf
contours, and use a cache key that is independent
of the size.
2023-09-23 20:16:12 -04:00
Christian Hergert
1e1de9ff77 gsk/gl: Start on basic glyphy renderjob integration
This doesn't work correctly yet, as there are lots of
bumps along the way to still smooth out.
2023-09-23 20:16:12 -04:00
Christian Hergert
57df4b6fe4 gsk/gl: Add a texture library for Glyphy
This adds a new texture library that can upload SDF data
from libglyphy into regions of a texture atlas so that it
can be accessed by Glyphy shaders in the appropriate place
and format.

Some of the placement positioning may seem odd in that it
needs to follow a certain format to be decoded from the
Glyphy shaders.
2023-09-23 20:16:12 -04:00
Christian Hergert
16d0f86f6a gsk/gl: Dispatch text_node to legacy vs glyphy
If the text node has color glyphs, then we need to dispatch
to the legacy form of rendering which uses FreeType/Cairo/etc
to upload glyphs to a rendered glyph cache.

Otherwise, we can dispatch to a new function which will
eventually use Glyphy to shape to SDF content and upload
to an alternate texture atlas.
2023-09-23 20:16:12 -04:00
Matthias Clasen
4cc3ab12ff build: Add a dependency on glyphy
We have a subproject, and we link statically
if we can, to avoid depending on a project
that is not generally packaged in distros.
2023-09-23 20:16:12 -04:00
Matthias Clasen
08b786a984 tools: Add more commands to gtk4-path-tool
Add pathops.
2023-09-23 20:15:33 -04:00
Matthias Clasen
54bf63259c gtk-demo: Add a demo for path ops
This demo is called Glyphs, since that is what
it works with.
2023-09-23 20:15:33 -04:00
Matthias Clasen
53b5441e39 Add some tests for pathops 2023-09-23 20:15:33 -04:00
Matthias Clasen
3b2ab7ad1e Add API for boolean operations on paths
The new APIs here are:
gsk_path_union
gsk_path_intersection
gsk_path_difference
gsk_path_symmetric_difference
gsk_path_simplify
2023-09-23 20:15:33 -04:00
Matthias Clasen
42eaacbab4 Implement boolean operations on paths
Implement union, intersection, difference and
symmetric difference of two paths, as well as
simplification of a single path.
2023-09-23 20:15:33 -04:00
Matthias Clasen
6a1660b78e path-tool: Show intersections 2023-09-23 20:14:45 -04:00
Matthias Clasen
821fc35941 path-tool: Allow showing two paths 2023-09-23 20:14:45 -04:00
Matthias Clasen
af0492ed0c Add a path intersection demo 2023-09-23 20:14:45 -04:00
Matthias Clasen
dcdbb73702 Add some path intersection tests 2023-09-23 20:14:45 -04:00
Matthias Clasen
60b2cf5d4a Add gsk_path_foreach_intersection
This function makes it possible to iterate
through the intersections of two paths.
2023-09-23 20:14:45 -04:00
Matthias Clasen
2fcb20cc4a contour: Add private api to circle contours
Add api to retrieve the parameters of a circle
contour. This will be used in the following
commits.
2023-09-23 15:29:12 -04:00
Matthias Clasen
89567767c5 Add tests for gsk_curve_intersect 2023-09-23 15:29:12 -04:00
Matthias Clasen
6577f386b0 curve: Add gsk_curve_intersect
Add a way to find the intersections of two curves.
We can handle some curve-line intersections directly,
the general case is handled via bisection.
2023-09-23 15:29:12 -04:00
Matthias Clasen
6ce8bd6581 curve: Some refactoring
Move cusp-related code to gskcurveintersect.c.
Add functions to find cusps and inflection points of cubics.
These will be used for intersections and in the stroker.
2023-09-23 15:29:12 -04:00
48 changed files with 8841 additions and 212 deletions

View File

@@ -295,6 +295,7 @@
<file>gears.c</file>
<file>gestures.c</file>
<file>glarea.c</file>
<file>glyphs.c</file>
<file>gltransition.c</file>
<file>headerbar.c</file>
<file>hypertext.c</file>
@@ -339,6 +340,7 @@
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_spinner.c</file>
<file>path_sweep.c</file>
<file>path_walk.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
@@ -426,6 +428,10 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_sweep">
<file>path_sweep.ui</file>
<file compressed="true">path_world.txt</file>
</gresource>
<gresource prefix="/path_walk">
<file>path_walk.ui</file>
<file compressed="true">path_world.txt</file>

1186
demos/gtk-demo/glyphs.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@ demos = files([
'gestures.c',
'glarea.c',
'gltransition.c',
'glyphs.c',
'headerbar.c',
'hypertext.c',
'iconscroll.c',
@@ -75,6 +76,7 @@ demos = files([
'path_fill.c',
'path_maze.c',
'path_spinner.c',
'path_sweep.c',
'path_walk.c',
'path_text.c',
'peg_solitaire.c',

319
demos/gtk-demo/path_sweep.c Normal file
View File

@@ -0,0 +1,319 @@
/* Path/Sweep
*
* This demo shows how path intersections can be used.
*
* The world map that is used here is a path with 211 lines and 1569 cubic
* Bėzier segments in 121 contours.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_SWEEP (gtk_path_sweep_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathSweep, gtk_path_sweep, GTK, PATH_SWEEP, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_PATH,
N_PROPS
};
struct _GtkPathSweep
{
GtkWidget parent_instance;
GskPath *path;
graphene_rect_t bounds;
float y_pos;
gboolean in;
};
struct _GtkPathSweepClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathSweep, gtk_path_sweep, GTK_TYPE_WIDGET)
static gboolean
intersection_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
GskPathBuilder *builder = data;
graphene_point_t p;
gsk_path_point_get_position (point1, path1, &p);
gsk_path_builder_add_circle (builder, &p, 4);
return TRUE;
}
static GskPath *
get_intersection_path (GskPath *path1,
GskPath *path2)
{
GskPathBuilder *builder = gsk_path_builder_new ();
gsk_path_foreach_intersection (path1, path2, intersection_cb, builder);
return gsk_path_builder_to_path (builder);
}
static void
gtk_path_sweep_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
GskStroke *stroke;
if (self->path == NULL)
return;
gtk_snapshot_save (snapshot);
stroke = gsk_stroke_new (2.0);
gtk_snapshot_append_stroke (snapshot, self->path, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
if (self->in)
{
graphene_rect_t bounds;
GskPathBuilder *builder;
GskPath *line, *isecs;
gsk_path_get_stroke_bounds (self->path, stroke, &bounds);
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, bounds.origin.x, bounds.origin.y + self->y_pos);
gsk_path_builder_line_to (builder, bounds.origin.x + bounds.size.width, bounds.origin.y + self->y_pos);
line = gsk_path_builder_free_to_path (builder);
gtk_snapshot_append_stroke (snapshot, line, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
isecs = get_intersection_path (self->path, line);
gtk_snapshot_append_fill (snapshot, isecs, GSK_FILL_RULE_WINDING, &(GdkRGBA) { 1, 0, 0, 1 });
gtk_snapshot_append_stroke (snapshot, isecs, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
gsk_path_unref (isecs);
gsk_path_unref (line);
}
gsk_stroke_free (stroke);
gtk_snapshot_restore (snapshot);
}
static void
gtk_path_sweep_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = (int) ceilf (self->bounds.size.width);
else
*minimum = *natural = (int) ceilf (self->bounds.size.height);
}
static void
gtk_path_sweep_set_path (GtkPathSweep *self,
GskPath *path)
{
if (self->path == path)
return;
g_clear_pointer (&self->path, gsk_path_unref);
graphene_rect_init (&self->bounds, 0, 0, 0, 0);
if (path)
{
GskStroke *stroke;
self->path = gsk_path_ref (path);
stroke = gsk_stroke_new (2.0);
gsk_path_get_stroke_bounds (path, stroke, &self->bounds);
gsk_stroke_free (stroke);
}
gtk_widget_queue_resize (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
}
static void
gtk_path_sweep_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
switch (prop_id)
{
case PROP_PATH:
gtk_path_sweep_set_path (self, g_value_get_boxed (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_sweep_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_sweep_dispose (GObject *object)
{
GtkPathSweep *self = GTK_PATH_SWEEP (object);
g_clear_pointer (&self->path, gsk_path_unref);
G_OBJECT_CLASS (gtk_path_sweep_parent_class)->dispose (object);
}
static void
gtk_path_sweep_class_init (GtkPathSweepClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_sweep_dispose;
object_class->set_property = gtk_path_sweep_set_property;
object_class->get_property = gtk_path_sweep_get_property;
widget_class->snapshot = gtk_path_sweep_snapshot;
widget_class->measure = gtk_path_sweep_measure;
properties[PROP_PATH] =
g_param_spec_boxed ("path", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
motion_cb (GtkEventControllerMotion *controller,
double x,
double y,
gpointer data)
{
GtkPathSweep *self = data;
self->y_pos = y;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
enter_cb (GtkEventControllerMotion *controller,
double x,
double y,
gpointer data)
{
GtkPathSweep *self = data;
self->in = TRUE;
self->y_pos = y;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
leave_cb (GtkEventControllerMotion *controller,
gpointer data)
{
GtkPathSweep *self = data;
self->in = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_sweep_init (GtkPathSweep *self)
{
GtkEventController *controller;
/* Data taken from
* https://commons.wikimedia.org/wiki/Maps_of_the_world#/media/File:Simplified_blank_world_map_without_Antartica_(no_borders).svg
*/
GBytes *data = g_resources_lookup_data ("/path_sweep/path_world.txt", 0, NULL);
GskPath *path = gsk_path_parse (g_bytes_get_data (data, NULL));
g_bytes_unref (data);
gtk_path_sweep_set_path (self, path);
gsk_path_unref (path);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), self);
g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
}
GtkWidget *
gtk_path_sweep_new (void)
{
GtkPathSweep *self;
self = g_object_new (GTK_TYPE_PATH_SWEEP, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_sweep (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_SWEEP);
builder = gtk_builder_new_from_resource ("/path_sweep/path_sweep.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,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">World Map</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkPathSweep" id="view">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</property>
</object>
</interface>

View File

@@ -12,6 +12,11 @@ SYNOPSIS
--------
| **gtk4-path-tool** <COMMAND> [OPTIONS...] <PATH>
|
| **gtk4-path-tool** simplify [OPTIONS...] <PATH>
| **gtk4-path-tool** intersection [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** union [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** difference [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** symmetric-difference [OPTIONS...] <PATH> <PATH>
| **gtk4-path-tool** decompose [OPTIONS...] <PATH>
| **gtk4-path-tool** show [OPTIONS...] <PATH>
| **gtk4-path-tool** render [OPTIONS...] <PATH>
@@ -195,6 +200,7 @@ The interior of the path is filled.
The limit at which to clip miters at line joins. The default value is 4.
<<<<<<< HEAD
``--dashes=VALUE``
The dash pattern to use for this stroke. A dash pattern is specified by
@@ -208,6 +214,21 @@ The interior of the path is filled.
The offset into the dash pattern where dashing should begin.
The default value is 0.
Boolean Operations
^^^^^^^^^^^^^^^^^^
The ``intersection``, ``union``, ``difference`` and ``symmetric-difference`` commands
perform boolean operations on paths. Given two paths, they create a new path which
encircles the area that is the intersection, union, difference or symmetric difference
of the areas encircled by the paths.
Simplification
^^^^^^^^^^^^^^
The ``simplify`` command removes areas of overlap from a path such that the resulting
path encircles the same area, but every edge in the resulting path is a boundary between
the inside and the outside.
Reversing
^^^^^^^^^

View File

@@ -32,6 +32,7 @@
#include "gskglcommandqueueprivate.h"
#include "gskglcompilerprivate.h"
#include "gskglglyphlibraryprivate.h"
#include "gskglglyphylibraryprivate.h"
#include "gskgliconlibraryprivate.h"
#include "gskglprogramprivate.h"
#include "gskglshadowlibraryprivate.h"
@@ -273,6 +274,7 @@ gsk_gl_driver_dispose (GObject *object)
}
g_clear_object (&self->glyphs_library);
g_clear_object (&self->glyphy_library);
g_clear_object (&self->icons_library);
g_clear_object (&self->shadows_library);
@@ -463,6 +465,7 @@ gsk_gl_driver_new (GskGLCommandQueue *command_queue,
}
self->glyphs_library = gsk_gl_glyph_library_new (self);
self->glyphy_library = gsk_gl_glyphy_library_new (self);
self->icons_library = gsk_gl_icon_library_new (self);
self->shadows_library = gsk_gl_shadow_library_new (self);
@@ -573,6 +576,8 @@ gsk_gl_driver_begin_frame (GskGLDriver *self,
self->current_frame_id);
gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs_library),
self->current_frame_id);
gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphy_library),
self->current_frame_id);
/* Cleanup old shadows */
gsk_gl_shadow_library_begin_frame (self->shadows_library);

View File

@@ -97,6 +97,7 @@ struct _GskGLDriver
GskGLCommandQueue *command_queue;
GskGLGlyphLibrary *glyphs_library;
GskGLGlyphyLibrary *glyphy_library;
GskGLIconLibrary *icons_library;
GskGLShadowLibrary *shadows_library;

544
gsk/gl/gskglglyphylibrary.c Normal file
View File

@@ -0,0 +1,544 @@
/* gskglglyphylibrary.c
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* 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 program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/* Some of the glyphy cache is based upon the original glyphy code.
* It's license is provided below.
*/
/*
* Copyright 2012 Google, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Google Author(s): Behdad Esfahbod
*/
#include "config.h"
#include <gdk/gdkglcontextprivate.h>
#include <gdk/gdkmemoryformatprivate.h>
#include <gdk/gdkprofilerprivate.h>
#include "gskglcommandqueueprivate.h"
#include "gskgldriverprivate.h"
#include "gskglglyphylibraryprivate.h"
#include "gskdebugprivate.h"
#include "gskpathprivate.h"
#include <glyphy.h>
#define TOLERANCE (1/2048.)
#define MIN_FONT_SIZE 14
#define GRID_SIZE 20 /* Per EM */
#define ENLIGHTEN_MAX .01 /* Per EM */
#define EMBOLDEN_MAX .024 /* Per EM */
/* We split the atlas into cells of size 64x8, so the minimum number of
* bytes we store per glyph is 2048, and an atlas of size 2048x1024 can
* hold at most 4096 glyphs. We need 5 and 7 bits to store the position
* of a glyph in the atlas.
*
* We allocate each glyph a column of as many vertically adjacent cells
* as it needs.
*/
#define ITEM_W 64
#define ITEM_H_QUANTUM 8
G_DEFINE_TYPE (GskGLGlyphyLibrary, gsk_gl_glyphy_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
GskGLGlyphyLibrary *
gsk_gl_glyphy_library_new (GskGLDriver *driver)
{
g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
return g_object_new (GSK_TYPE_GL_GLYPHY_LIBRARY,
"driver", driver,
NULL);
}
static guint
gsk_gl_glyphy_key_hash (gconstpointer data)
{
const GskGLGlyphyKey *key = data;
/* malloc()'d pointers already guarantee 3 bits from the LSB on 64-bit and
* 2 bits from the LSB on 32-bit. Shift by enough to give us 256 entries
* in our front cache for the glyph since languages will naturally cluster
* for us.
*/
return (key->font << 8) ^ key->glyph;
}
static gboolean
gsk_gl_glyphy_key_equal (gconstpointer v1,
gconstpointer v2)
{
return memcmp (v1, v2, sizeof (GskGLGlyphyKey)) == 0;
}
static void
gsk_gl_glyphy_key_free (gpointer data)
{
GskGLGlyphyKey *key = data;
g_slice_free (GskGLGlyphyKey, key);
}
static void
gsk_gl_glyphy_value_free (gpointer data)
{
g_slice_free (GskGLGlyphyValue, data);
}
static void
gsk_gl_glyphy_library_clear_cache (GskGLTextureLibrary *library)
{
GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)library;
g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self));
memset (self->front, 0, sizeof self->front);
}
static void
gsk_gl_glyphy_library_init_atlas (GskGLTextureLibrary *library,
GskGLTextureAtlas *atlas)
{
g_assert (GSK_IS_GL_GLYPHY_LIBRARY (library));
g_assert (atlas != NULL);
atlas->cursor_x = 0;
atlas->cursor_y = 0;
}
static gboolean
gsk_gl_glyphy_library_allocate (GskGLTextureLibrary *library,
GskGLTextureAtlas *atlas,
int width,
int height,
int *out_x,
int *out_y)
{
GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)library;
int cursor_save_x;
int cursor_save_y;
g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self));
g_assert (atlas != NULL);
cursor_save_x = atlas->cursor_x;
cursor_save_y = atlas->cursor_y;
if ((height & (self->item_h_q-1)) != 0)
height = (height + self->item_h_q - 1) & ~(self->item_h_q - 1);
/* Require allocations in columns of 64 and rows of 8 */
g_assert (width == self->item_w);
g_assert ((height % self->item_h_q) == 0);
if (atlas->cursor_y + height > atlas->height)
{
/* Go to next column */
atlas->cursor_x += self->item_w;
atlas->cursor_y = 0;
}
if (atlas->cursor_x + width <= atlas->width &&
atlas->cursor_y + height <= atlas->height)
{
*out_x = atlas->cursor_x;
*out_y = atlas->cursor_y;
atlas->cursor_y += height;
return TRUE;
}
atlas->cursor_x = cursor_save_x;
atlas->cursor_y = cursor_save_y;
return FALSE;
}
static void
gsk_gl_glyphy_library_finalize (GObject *object)
{
GskGLGlyphyLibrary *self = (GskGLGlyphyLibrary *)object;
g_clear_pointer (&self->acc, glyphy_arc_accumulator_destroy);
g_clear_pointer (&self->acc_endpoints, g_array_unref);
G_OBJECT_CLASS (gsk_gl_glyphy_library_parent_class)->finalize (object);
}
GQuark quark_glyphy_font_key;
static void
gsk_gl_glyphy_library_class_init (GskGLGlyphyLibraryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass);
quark_glyphy_font_key = g_quark_from_static_string ("glyphy-font-key");
object_class->finalize = gsk_gl_glyphy_library_finalize;
library_class->allocate = gsk_gl_glyphy_library_allocate;
library_class->clear_cache = gsk_gl_glyphy_library_clear_cache;
library_class->init_atlas = gsk_gl_glyphy_library_init_atlas;
}
static void
gsk_gl_glyphy_library_init (GskGLGlyphyLibrary *self)
{
GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
tl->max_entry_size = 0;
tl->max_frame_age = 512;
tl->atlas_width = 2048;
tl->atlas_height = 1024;
gsk_gl_texture_library_set_funcs (tl,
gsk_gl_glyphy_key_hash,
gsk_gl_glyphy_key_equal,
gsk_gl_glyphy_key_free,
gsk_gl_glyphy_value_free);
self->acc = glyphy_arc_accumulator_create ();
self->acc_endpoints = g_array_new (FALSE, FALSE, sizeof (glyphy_arc_endpoint_t));
self->item_w = ITEM_W;
self->item_h_q = ITEM_H_QUANTUM;
}
static glyphy_bool_t
accumulate_endpoint (glyphy_arc_endpoint_t *endpoint,
GArray *endpoints)
{
g_array_append_vals (endpoints, endpoint, 1);
return TRUE;
}
static void
move_to (hb_draw_funcs_t *dfuncs,
GskPathBuilder *builder,
hb_draw_state_t *st,
float x,
float y,
void *data)
{
gsk_path_builder_move_to (builder, x, y);
}
static void
line_to (hb_draw_funcs_t *dfuncs,
GskPathBuilder *builder,
hb_draw_state_t *st,
float x,
float y,
void *data)
{
gsk_path_builder_line_to (builder, x, y);
}
static void
cubic_to (hb_draw_funcs_t *dfuncs,
GskPathBuilder *builder,
hb_draw_state_t *st,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3,
void *data)
{
gsk_path_builder_cubic_to (builder, x1, y1, x2, y2, x3, y3);
}
static void
close_path (hb_draw_funcs_t *dfuncs,
GskPathBuilder *builder,
hb_draw_state_t *st,
void *data)
{
gsk_path_builder_close (builder);
}
static hb_draw_funcs_t *
gsk_path_get_draw_funcs (void)
{
static hb_draw_funcs_t *funcs = NULL;
if (!funcs)
{
funcs = hb_draw_funcs_create ();
hb_draw_funcs_set_move_to_func (funcs, (hb_draw_move_to_func_t) move_to, NULL, NULL);
hb_draw_funcs_set_line_to_func (funcs, (hb_draw_line_to_func_t) line_to, NULL, NULL);
hb_draw_funcs_set_cubic_to_func (funcs, (hb_draw_cubic_to_func_t) cubic_to, NULL, NULL);
hb_draw_funcs_set_close_path_func (funcs, (hb_draw_close_path_func_t) close_path, NULL, NULL);
hb_draw_funcs_make_immutable (funcs);
}
return funcs;
}
static gboolean
acc_callback (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
glyphy_arc_accumulator_t *acc = user_data;
glyphy_point_t p0, p1, p2, p3;
switch (op)
{
case GSK_PATH_MOVE:
p0.x = pts[0].x; p0.y = pts[0].y;
glyphy_arc_accumulator_move_to (acc, &p0);
break;
case GSK_PATH_CLOSE:
glyphy_arc_accumulator_close_path (acc);
break;
case GSK_PATH_LINE:
p1.x = pts[1].x; p1.y = pts[1].y;
glyphy_arc_accumulator_line_to (acc, &p1);
break;
case GSK_PATH_QUAD:
p1.x = pts[1].x; p1.y = pts[1].y;
p2.x = pts[2].x; p2.y = pts[2].y;
/* This glyphy api is mis-named */
glyphy_arc_accumulator_conic_to (acc, &p1, &p2);
break;
case GSK_PATH_CUBIC:
p1.x = pts[1].x; p1.y = pts[1].y;
p2.x = pts[2].x; p2.y = pts[2].y;
p3.x = pts[3].x; p3.y = pts[3].y;
glyphy_arc_accumulator_cubic_to (acc, &p1, &p2, &p3);
break;
case GSK_PATH_CONIC:
default:
g_assert_not_reached ();
}
return TRUE;
}
static inline gboolean
encode_glyph (GskGLGlyphyLibrary *self,
hb_font_t *font,
unsigned int glyph_index,
double tolerance_per_em,
glyphy_rgba_t *buffer,
guint buffer_len,
guint *output_len,
guint *nominal_width,
guint *nominal_height,
glyphy_extents_t *extents)
{
hb_face_t *face = hb_font_get_face (font);
guint upem = hb_face_get_upem (face);
double tolerance = upem * tolerance_per_em;
double faraway = (double)upem / (MIN_FONT_SIZE * M_SQRT2);
double unit_size = (double) upem / GRID_SIZE;
double enlighten_max = (double) upem * ENLIGHTEN_MAX;
double embolden_max = (double) upem * EMBOLDEN_MAX;
double avg_fetch_achieved;
GskPathBuilder *builder;
GskPath *path, *simplified;
self->acc_endpoints->len = 0;
glyphy_arc_accumulator_reset (self->acc);
glyphy_arc_accumulator_set_tolerance (self->acc, tolerance);
glyphy_arc_accumulator_set_callback (self->acc,
(glyphy_arc_endpoint_accumulator_callback_t)accumulate_endpoint,
self->acc_endpoints);
builder = gsk_path_builder_new ();
#if HB_VERSION_ATLEAST (7, 0, 0)
hb_font_draw_glyph (font, glyph_index, gsk_path_get_draw_funcs (), builder);
#else
hb_font_get_glyph_shape (font, glyph_index, gsk_path_get_draw_funcs (), builder);
#endif
path = gsk_path_builder_free_to_path (builder);
simplified = gsk_path_op (GSK_PATH_OP_SIMPLIFY, GSK_FILL_RULE_WINDING, path, NULL);
gsk_path_foreach (simplified, -1, acc_callback, self->acc);
gsk_path_unref (simplified);
gsk_path_unref (path);
if (!glyphy_arc_accumulator_successful (self->acc))
return FALSE;
g_assert (glyphy_arc_accumulator_get_error (self->acc) <= tolerance);
if (self->acc_endpoints->len > 0)
glyphy_outline_winding_from_even_odd ((gpointer)self->acc_endpoints->data,
self->acc_endpoints->len,
FALSE);
if (!glyphy_arc_list_encode_blob2 ((gpointer)self->acc_endpoints->data,
self->acc_endpoints->len,
buffer,
buffer_len,
faraway,
unit_size,
enlighten_max,
embolden_max,
&avg_fetch_achieved,
output_len,
nominal_width,
nominal_height,
extents))
return FALSE;
glyphy_extents_scale (extents, 1./upem, 1./upem);
return TRUE;
}
static inline hb_font_t *
get_nominal_size_hb_font (PangoFont *font)
{
hb_font_t *hbfont;
const float *coords;
unsigned int length;
hbfont = (hb_font_t *) g_object_get_data ((GObject *)font, "glyph-nominal-size-font");
if (hbfont == NULL)
{
hbfont = hb_font_create (hb_font_get_face (pango_font_get_hb_font (font)));
coords = hb_font_get_var_coords_design (pango_font_get_hb_font (font), &length);
if (length > 0)
hb_font_set_var_coords_design (hbfont, coords, length);
g_object_set_data_full ((GObject *)font, "glyphy-nominal-size-font",
hbfont, (GDestroyNotify)hb_font_destroy);
}
return hbfont;
}
gboolean
gsk_gl_glyphy_library_add (GskGLGlyphyLibrary *self,
GskGLGlyphyKey *key,
PangoFont *font,
const GskGLGlyphyValue **out_value)
{
static glyphy_rgba_t buffer[4096 * 16];
GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
GskGLGlyphyValue *value;
glyphy_extents_t extents;
hb_font_t *hbfont;
guint packed_x;
guint packed_y;
guint nominal_w, nominal_h;
guint output_len = 0;
guint texture_id;
guint width, height;
g_assert (GSK_IS_GL_GLYPHY_LIBRARY (self));
g_assert (key != NULL);
g_assert (font != NULL);
g_assert (out_value != NULL);
hbfont = get_nominal_size_hb_font (font);
/* Convert the glyph to a list of arcs */
if (!encode_glyph (self, hbfont, key->glyph, TOLERANCE,
buffer, sizeof buffer, &output_len,
&nominal_w, &nominal_h, &extents))
return FALSE;
/* Allocate space for list within atlas */
width = self->item_w;
height = (output_len + width - 1) / width;
GSK_DEBUG (GLYPH_CACHE, "font %u glyph %u: %u bytes (%u x %u)", key->font, key->glyph, output_len * 4, width, height);
value = gsk_gl_texture_library_pack (tl, key, sizeof *value,
width, height, 0,
&packed_x, &packed_y);
g_assert (packed_x % ITEM_W == 0);
g_assert (packed_y % ITEM_H_QUANTUM == 0);
/* Make sure we found space to pack */
texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value);
if (texture_id == 0)
return FALSE;
if (!glyphy_extents_is_empty (&extents))
{
/* Connect the texture for data upload */
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, texture_id);
g_assert (width > 0);
g_assert (height > 0);
/* Upload the arc list */
if (width * height == output_len)
{
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y,
width, height,
GL_RGBA, GL_UNSIGNED_BYTE,
buffer);
}
else
{
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y,
width, height - 1,
GL_RGBA, GL_UNSIGNED_BYTE,
buffer);
/* Upload the last row separately */
glTexSubImage2D (GL_TEXTURE_2D, 0,
packed_x, packed_y + height - 1,
output_len - (width * (height - 1)), 1,
GL_RGBA, GL_UNSIGNED_BYTE,
buffer + (width * (height - 1)));
}
}
value->extents.min_x = extents.min_x;
value->extents.min_y = extents.min_y;
value->extents.max_x = extents.max_x;
value->extents.max_y = extents.max_y;
value->nominal_w = nominal_w;
value->nominal_h = nominal_h;
value->atlas_x = packed_x / self->item_w;
value->atlas_y = packed_y / self->item_h_q;
*out_value = value;
return TRUE;
}

View File

@@ -0,0 +1,142 @@
/* gskglglyphylibraryprivate.h
*
* Copyright 2020-2022 Christian Hergert <chergert@redhat.com>
*
* 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 program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
#define __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
#include <glyphy.h>
#include <pango/pango.h>
#include "gskgltexturelibraryprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_GLYPHY_LIBRARY (gsk_gl_glyphy_library_get_type())
typedef guint FontKey;
extern GQuark quark_glyphy_font_key;
static inline FontKey
gsk_gl_glyphy_library_get_font_key (PangoFont *font)
{
FontKey key;
key = (FontKey) GPOINTER_TO_UINT (g_object_get_qdata ((GObject *)font, quark_glyphy_font_key));
if (key == 0)
{
PangoFontDescription *desc = pango_font_describe (font);
pango_font_description_set_size (desc, 10 * PANGO_SCALE);
key = (FontKey) pango_font_description_hash (desc);
pango_font_description_free (desc);
g_object_set_qdata ((GObject *)font, quark_glyphy_font_key, GUINT_TO_POINTER (key));
}
return key;
}
static inline float
gsk_gl_glyphy_library_get_font_scale (PangoFont *font)
{
hb_font_t *hbfont;
int x_scale, y_scale;
hbfont = pango_font_get_hb_font (font);
hb_font_get_scale (hbfont, &x_scale, &y_scale);
return MAX (x_scale, y_scale) / 1000.0;
}
typedef struct _GskGLGlyphyKey
{
FontKey font;
PangoGlyph glyph;
} GskGLGlyphyKey;
typedef struct _GskGLGlyphyValue
{
GskGLTextureAtlasEntry entry;
struct {
float min_x;
float min_y;
float max_x;
float max_y;
} extents;
guint nominal_w;
guint nominal_h;
guint atlas_x;
guint atlas_y;
} GskGLGlyphyValue;
G_DECLARE_FINAL_TYPE (GskGLGlyphyLibrary, gsk_gl_glyphy_library, GSK, GL_GLYPHY_LIBRARY, GskGLTextureLibrary)
struct _GskGLGlyphyLibrary
{
GskGLTextureLibrary parent_instance;
glyphy_arc_accumulator_t *acc;
GArray *acc_endpoints;
guint item_w;
guint item_h_q;
struct {
GskGLGlyphyKey key;
const GskGLGlyphyValue *value;
} front[256];
};
GskGLGlyphyLibrary *gsk_gl_glyphy_library_new (GskGLDriver *driver);
gboolean gsk_gl_glyphy_library_add (GskGLGlyphyLibrary *self,
GskGLGlyphyKey *key,
PangoFont *font,
const GskGLGlyphyValue **out_value);
static inline guint
gsk_gl_glyphy_library_lookup_or_add (GskGLGlyphyLibrary *self,
const GskGLGlyphyKey *key,
PangoFont *font,
const GskGLGlyphyValue **out_value)
{
GskGLTextureAtlasEntry *entry;
guint front_index = key->glyph & 0xFF;
if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
{
*out_value = self->front[front_index].value;
}
else if (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry))
{
*out_value = (GskGLGlyphyValue *)entry;
self->front[front_index].key = *key;
self->front[front_index].value = *out_value;
}
else
{
GskGLGlyphyKey *k = g_slice_copy (sizeof *key, key);
gsk_gl_glyphy_library_add (self, k, font, out_value);
self->front[front_index].key = *key;
self->front[front_index].value = *out_value;
}
return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value);
}
G_END_DECLS
#endif /* __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__ */

View File

@@ -87,3 +87,21 @@ GSK_GL_DEFINE_PROGRAM (unblurred_outset_shadow,
GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
GSK_GL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
GSK_GL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
GSK_GL_DEFINE_PROGRAM (glyphy,
GSK_GL_SHADER_JOINED (VERTEX,
GSK_GL_SHADER_RESOURCE ("glyphy.vs.glsl"),
NULL)
GSK_GL_SHADER_JOINED (FRAGMENT,
GSK_GL_SHADER_RESOURCE ("glyphy.atlas.glsl"),
GSK_GL_SHADER_STRING (glyphy_common_shader_source ()),
GSK_GL_SHADER_STRING ("#define GLYPHY_SDF_PSEUDO_DISTANCE 1\n"),
GSK_GL_SHADER_STRING (glyphy_sdf_shader_source ()),
GSK_GL_SHADER_RESOURCE ("glyphy.fs.glsl"),
NULL),
GSK_GL_ADD_UNIFORM (0, GLYPHY_CONTRAST, u_contrast)
GSK_GL_ADD_UNIFORM (1, GLYPHY_GAMMA_ADJUST, u_gamma_adjust)
GSK_GL_ADD_UNIFORM (2, GLYPHY_OUTLINE_THICKNESS, u_outline_thickness)
GSK_GL_ADD_UNIFORM (3, GLYPHY_OUTLINE, u_outline)
GSK_GL_ADD_UNIFORM (4, GLYPHY_BOLDNESS, u_boldness)
GSK_GL_ADD_UNIFORM (6, GLYPHY_ATLAS_INFO, u_atlas_info))

View File

@@ -150,6 +150,13 @@ gsk_gl_renderer_realize (GskRenderer *renderer,
gsk_gl_command_queue_set_profiler (self->command_queue,
gsk_renderer_get_profiler (renderer));
#ifdef G_ENABLE_DEBUG
if (gsk_renderer_get_debug_flags (renderer) & GSK_DEBUG_NO_GLYPHY)
GSK_RENDERER_DEBUG (renderer, RENDERER, "GL Renderer will use cairo for glyph rendering");
else
GSK_RENDERER_DEBUG (renderer, RENDERER, "GL Renderer will use glyphy for glyph rendering");
#endif
ret = TRUE;
failure:
@@ -307,10 +314,15 @@ gsk_gl_renderer_render (GskRenderer *renderer,
gsk_gl_driver_begin_frame (self->driver, self->command_queue);
job = gsk_gl_render_job_new (self->driver, &viewport, scale, render_region, 0, clear_framebuffer);
if ((gsk_renderer_get_debug_flags (renderer) & GSK_DEBUG_NO_GLYPHY) == 0)
gsk_gl_render_job_set_use_glyphy (job, TRUE);
#ifdef G_ENABLE_DEBUG
if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
gsk_gl_render_job_set_debug_fallback (job, TRUE);
#endif
gsk_gl_render_job_render (job, root);
gsk_gl_driver_end_frame (self->driver);
gsk_gl_render_job_free (job);

View File

@@ -35,10 +35,14 @@
#include <gsk/gskrectprivate.h>
#include <math.h>
#include <string.h>
#ifdef HAVE_PANGOFT
#include <pango/pangofc-font.h>
#endif
#include "gskglcommandqueueprivate.h"
#include "gskgldriverprivate.h"
#include "gskglglyphlibraryprivate.h"
#include "gskglglyphylibraryprivate.h"
#include "gskgliconlibraryprivate.h"
#include "gskglprogramprivate.h"
#include "gskglrenderjobprivate.h"
@@ -152,6 +156,9 @@ struct _GskGLRenderJob
*/
guint clear_framebuffer : 1;
/* Allow experimental glyph rendering with glyphy */
guint use_glyphy : 1;
/* Format we want to use for intermediate textures, determined by
* looking at the format of the framebuffer we are rendering on.
*/
@@ -2953,10 +2960,10 @@ compute_phase_and_pos (float value, float *pos)
}
static inline void
gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
gsk_gl_render_job_visit_text_node_legacy (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
{
const PangoFont *font = gsk_text_node_get_font (node);
const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
@@ -3093,6 +3100,263 @@ gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
}
}
/* Keep this in sync with glyph_vertex_transcode in glyphy.vs.glsl */
typedef struct
{
float x;
float y;
float g16hi;
float g16lo;
} EncodedGlyph;
static inline unsigned int
glyph_encode (guint atlas_x , /* 7 bits */
guint atlas_y, /* 7 bits */
guint corner_x, /* 1 bit */
guint corner_y, /* 1 bit */
guint nominal_w, /* 6 bits */
guint nominal_h) /* 6 bits */
{
guint x, y;
g_assert (0 == (atlas_x & ~0x7F));
g_assert (0 == (atlas_y & ~0x7F));
g_assert (0 == (corner_x & ~1));
g_assert (0 == (corner_y & ~1));
g_assert (0 == (nominal_w & ~0x3F));
g_assert (0 == (nominal_h & ~0x3F));
x = (((atlas_x << 6) | nominal_w) << 1) | corner_x;
y = (((atlas_y << 6) | nominal_h) << 1) | corner_y;
return (x << 16) | y;
}
static inline void
encoded_glyph_init (EncodedGlyph *eg,
float x,
float y,
guint corner_x,
guint corner_y,
const GskGLGlyphyValue *gi)
{
guint encoded = glyph_encode (gi->atlas_x, gi->atlas_y, corner_x, corner_y, gi->nominal_w, gi->nominal_h);
eg->x = x;
eg->y = y;
eg->g16hi = encoded >> 16;
eg->g16lo = encoded & 0xFFFF;
}
static inline void
add_encoded_glyph (GskGLDrawVertex *vertices,
const EncodedGlyph *eg,
const guint16 c[4])
{
*vertices = (GskGLDrawVertex) { .position = { eg->x, eg->y}, .uv = { eg->g16hi, eg->g16lo}, .color = { c[0], c[1], c[2], c[3] } };
}
static void
get_synthetic_font_params (PangoFont *font,
gboolean *embolden,
PangoMatrix *matrix)
{
*embolden = FALSE;
*matrix = (PangoMatrix) PANGO_MATRIX_INIT;
#ifdef HAVE_PANGOFT
if (PANGO_IS_FC_FONT (font))
{
FcPattern *pattern = pango_fc_font_get_pattern (PANGO_FC_FONT (font));
FcBool b;
FcMatrix mat;
FcMatrix *m;
if (FcPatternGetBool (pattern, FC_EMBOLDEN, 0, &b) == FcResultMatch)
*embolden = b;
FcMatrixInit (&mat);
for (int i = 0; FcPatternGetMatrix (pattern, FC_MATRIX, i, &m) == FcResultMatch; i++)
FcMatrixMultiply (&mat, &mat, m);
matrix->xx = mat.xx;
matrix->xy = mat.xy;
matrix->yx = mat.yx;
matrix->yy = mat.yy;
}
#endif
}
static inline void
gsk_gl_render_job_visit_text_node_glyphy (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color)
{
const graphene_point_t *offset;
const PangoGlyphInfo *glyphs;
const PangoGlyphInfo *gi;
GskGLGlyphyLibrary *library;
GskGLCommandBatch *batch;
PangoFont *font;
GskGLDrawVertex *vertices;
const guint16 *c;
GskGLGlyphyKey lookup;
guint16 cc[4];
float x;
float y;
guint last_texture = 0;
guint num_glyphs;
guint used = 0;
guint i;
int x_position = 0;
float font_scale;
gboolean embolden;
PangoMatrix matrix = PANGO_MATRIX_INIT;
#define GRID_SIZE 20
g_assert (!gsk_text_node_has_color_glyphs (node));
if (!(num_glyphs = gsk_text_node_get_num_glyphs (node)))
return;
if (RGBA_IS_CLEAR (color))
return;
font = (PangoFont *)gsk_text_node_get_font (node);
get_synthetic_font_params (font, &embolden, &matrix);
glyphs = gsk_text_node_get_glyphs (node, NULL);
library = job->driver->glyphy_library;
offset = gsk_text_node_get_offset (node);
x = offset->x + job->offset_x;
y = offset->y + job->offset_y;
rgba_to_half (color, cc);
c = cc;
gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, glyphy));
batch = gsk_gl_command_queue_get_batch (job->command_queue);
vertices = gsk_gl_command_queue_add_n_vertices (job->command_queue, num_glyphs);
lookup.font = gsk_gl_glyphy_library_get_font_key (font);
font_scale = gsk_gl_glyphy_library_get_font_scale (font);
for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++)
{
const GskGLGlyphyValue *glyph;
float cx = 0, cy = 0;
guint texture_id;
lookup.glyph = gi->glyph;
texture_id = gsk_gl_glyphy_library_lookup_or_add (library, &lookup, font, &glyph);
if G_UNLIKELY (texture_id == 0)
continue;
if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff)
{
if G_LIKELY (last_texture != 0)
{
guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count;
/* Since we have batched added our VBO vertices to avoid repeated
* calls to the buffer, we need to manually tweak the vbo offset
* of the new batch as otherwise it will point at the end of our
* vbo array.
*/
gsk_gl_render_job_split_draw (job);
batch = gsk_gl_command_queue_get_batch (job->command_queue);
batch->draw.vbo_offset = vbo_offset;
}
gsk_gl_program_set_uniform4i (job->current_program,
UNIFORM_GLYPHY_ATLAS_INFO, 0,
GSK_GL_TEXTURE_LIBRARY (library)->atlas_width,
GSK_GL_TEXTURE_LIBRARY (library)->atlas_height,
library->item_w,
library->item_h_q);
gsk_gl_program_set_uniform_texture (job->current_program,
UNIFORM_SHARED_SOURCE, 0,
GL_TEXTURE_2D,
GL_TEXTURE0,
texture_id);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_GAMMA_ADJUST, 0,
1.0);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_CONTRAST, 0,
1.0);
/* 0.0208 is the value used by freetype for synthetic emboldening */
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_BOLDNESS, 0,
embolden ? 0.0208 * GRID_SIZE : 0.0);
#if 0
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_OUTLINE_THICKNESS, 0,
1.0);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_OUTLINE, 0,
1.0);
#endif
last_texture = texture_id;
}
cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
if G_UNLIKELY (gi->geometry.y_offset != 0)
cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
x_position += gi->geometry.width;
EncodedGlyph encoded[4];
#define ENCODE_CORNER(_cx, _cy) \
G_STMT_START { \
float _dx = _cx * (glyph->extents.max_x - glyph->extents.min_x); \
float _dy = _cy * (glyph->extents.max_y - glyph->extents.min_y); \
float _vx = x + cx + font_scale * (glyph->extents.min_x + matrix.xx * _dx + matrix.xy * _dy); \
float _vy = y + cy - font_scale * (glyph->extents.min_y + matrix.yx * _dx + matrix.yy * _dy); \
encoded_glyph_init (&encoded[_cx * 2 + _cy], _vx, _vy, _cx, _cy, glyph); \
} G_STMT_END
ENCODE_CORNER (0, 0);
ENCODE_CORNER (0, 1);
ENCODE_CORNER (1, 0);
ENCODE_CORNER (1, 1);
#undef ENCODE_CORNER
add_encoded_glyph (vertices++, &encoded[0], c);
add_encoded_glyph (vertices++, &encoded[1], c);
add_encoded_glyph (vertices++, &encoded[2], c);
add_encoded_glyph (vertices++, &encoded[1], c);
add_encoded_glyph (vertices++, &encoded[2], c);
add_encoded_glyph (vertices++, &encoded[3], c);
batch->draw.vbo_count += GSK_GL_N_VERTICES;
used++;
}
if (used != num_glyphs)
gsk_gl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used);
gsk_gl_render_job_end_draw (job);
}
static inline void
gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
{
if (job->use_glyphy && !gsk_text_node_has_color_glyphs (node))
gsk_gl_render_job_visit_text_node_glyphy (job, node, color);
else
gsk_gl_render_job_visit_text_node_legacy (job, node, color, force_color);
}
static inline void
gsk_gl_render_job_visit_shadow_node (GskGLRenderJob *job,
const GskRenderNode *node)
@@ -4476,6 +4740,15 @@ gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
job->debug_fallback = !!debug_fallback;
}
void
gsk_gl_render_job_set_use_glyphy (GskGLRenderJob *job,
gboolean use_glyphy)
{
g_return_if_fail (job != NULL);
job->use_glyphy = !!use_glyphy;
}
static int
get_framebuffer_format (GdkGLContext *context,
guint framebuffer)

View File

@@ -35,4 +35,5 @@ void gsk_gl_render_job_render_flipped (GskGLRenderJob *job
GskRenderNode *root);
void gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
gboolean debug_fallback);
void gsk_gl_render_job_set_use_glyphy (GskGLRenderJob *job,
gboolean use_glyphy);

View File

@@ -36,9 +36,14 @@ G_BEGIN_DECLS
typedef struct _GskGLTextureAtlas
{
/* Used by Glyph/Icons */
struct stbrp_context context;
struct stbrp_node *nodes;
/* Used by Glyphy */
int cursor_x;
int cursor_y;
int width;
int height;
@@ -48,7 +53,6 @@ typedef struct _GskGLTextureAtlas
* But are now unused.
*/
int unused_pixels;
} GskGLTextureAtlas;
typedef struct _GskGLTextureAtlasEntry

View File

@@ -36,6 +36,7 @@ typedef struct _GskGLCompiler GskGLCompiler;
typedef struct _GskGLDrawVertex GskGLDrawVertex;
typedef struct _GskGLRenderTarget GskGLRenderTarget;
typedef struct _GskGLGlyphLibrary GskGLGlyphLibrary;
typedef struct _GskGLGlyphyLibrary GskGLGlyphyLibrary;
typedef struct _GskGLIconLibrary GskGLIconLibrary;
typedef struct _GskGLProgram GskGLProgram;
typedef struct _GskGLRenderJob GskGLRenderJob;

View File

@@ -0,0 +1,15 @@
uniform ivec4 u_atlas_info;
#define GLYPHY_TEXTURE1D_EXTRA_DECLS , sampler2D _tex, ivec4 _atlas_info, ivec2 _atlas_pos
#define GLYPHY_TEXTURE1D_EXTRA_ARGS , _tex, _atlas_info, _atlas_pos
#define GLYPHY_DEMO_EXTRA_ARGS , u_source, u_atlas_info, gi.atlas_pos
vec4
glyphy_texture1D_func (int offset GLYPHY_TEXTURE1D_EXTRA_DECLS)
{
ivec2 item_geom = _atlas_info.zw;
vec2 pos = (vec2 (_atlas_pos.xy * item_geom +
ivec2 (mod (float (offset), float (item_geom.x)), offset / item_geom.x)) +
+ vec2 (.5, .5)) / vec2(_atlas_info.xy);
return GskTexture (_tex, pos);
}

View File

@@ -0,0 +1,62 @@
// FRAGMENT_SHADER:
// glyphy.fs.glsl
uniform float u_contrast;
uniform float u_gamma_adjust;
uniform float u_outline_thickness;
uniform bool u_outline;
uniform float u_boldness;
_IN_ vec4 v_glyph;
_IN_ vec4 final_color;
#define SQRT2 1.4142135623730951
#define SQRT2_INV 0.70710678118654757 /* 1 / sqrt(2.) */
struct glyph_info_t {
ivec2 nominal_size;
ivec2 atlas_pos;
};
glyph_info_t
glyph_info_decode (vec4 v)
{
glyph_info_t gi;
gi.nominal_size = (ivec2 (mod (v.zw, 256.)) + 2) / 4;
gi.atlas_pos = ivec2 (v_glyph.zw) / 256;
return gi;
}
float
antialias (float d)
{
return smoothstep (-.75, +.75, d);
}
void
main()
{
vec2 p = v_glyph.xy;
glyph_info_t gi = glyph_info_decode (v_glyph);
/* isotropic antialiasing */
vec2 dpdx = dFdx (p);
vec2 dpdy = dFdy (p);
float m = length (vec2 (length (dpdx), length (dpdy))) * SQRT2_INV;
float gsdist = glyphy_sdf (p, gi.nominal_size GLYPHY_DEMO_EXTRA_ARGS);
gsdist -= u_boldness;
float sdist = gsdist / m * u_contrast;
if (u_outline)
sdist = abs (sdist) - u_outline_thickness * .5;
if (sdist > 1.)
discard;
float alpha = antialias (-sdist);
if (u_gamma_adjust != 1.)
alpha = pow (alpha, 1./u_gamma_adjust);
gskSetOutputColor(final_color * alpha);
}

View File

@@ -0,0 +1,25 @@
// VERTEX_SHADER:
// glyphy.vs.glsl
_OUT_ vec4 v_glyph;
_OUT_ vec4 final_color;
// Keep this in sync with glyph_encode in gskglrenderjob.c
vec4
glyph_vertex_transcode (vec2 v)
{
ivec2 g = ivec2 (v);
ivec2 corner = ivec2 (mod (v, 2.));
g /= 2;
ivec2 nominal_size = ivec2 (mod (vec2(g), 64.));
return vec4 (corner * nominal_size, g * 4);
}
void
main()
{
v_glyph = glyph_vertex_transcode(aUv);
vUv = v_glyph.zw;
gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
final_color = gsk_scaled_premultiply(aColor, u_alpha);
}

View File

@@ -1,3 +1,5 @@
#extension GL_OES_standard_derivatives : enable
#ifndef GSK_LEGACY
precision highp float;
#endif

View File

@@ -1749,6 +1749,19 @@ gsk_circle_contour_new (const graphene_point_t *center,
return (GskContour *) self;
}
void
gsk_circle_contour_get_params (const GskContour *contour,
graphene_point_t *center,
float *radius,
gboolean *ccw)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
*center = self->center;
*radius = self->radius;
*ccw = self->ccw;
}
/* }}} */
/* {{{ Rectangle */

View File

@@ -36,6 +36,10 @@ GskContour * gsk_standard_contour_new (GskPathFlags
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius);
void gsk_circle_contour_get_params (const GskContour *contour,
graphene_point_t *center,
float *radius,
gboolean *ccw);
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);

View File

@@ -2330,6 +2330,30 @@ gsk_curve_get_crossing (const GskCurve *curve,
return get_class (curve->op)->get_crossing (curve, point);
}
float
gsk_curve_get_length_to (const GskCurve *curve,
float t)
{
return get_class (curve->op)->get_length_to (curve, t);
}
float
gsk_curve_get_length (const GskCurve *curve)
{
return gsk_curve_get_length_to (curve, 1);
}
float
gsk_curve_at_length (const GskCurve *curve,
float length,
float epsilon)
{
return get_class (curve->op)->get_at_length (curve, length, epsilon);
}
/* }}} */
/* {{{ Closest point */
static gboolean
project_point_onto_line (const GskCurve *curve,
const graphene_point_t *point,
@@ -2451,187 +2475,6 @@ gsk_curve_get_closest_point (const GskCurve *curve,
return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t);
}
float
gsk_curve_get_length_to (const GskCurve *curve,
float t)
{
return get_class (curve->op)->get_length_to (curve, t);
}
float
gsk_curve_get_length (const GskCurve *curve)
{
return gsk_curve_get_length_to (curve, 1);
}
/* Compute the inverse of the arclength using bisection,
* to a given precision
*/
float
gsk_curve_at_length (const GskCurve *curve,
float length,
float epsilon)
{
return get_class (curve->op)->get_at_length (curve, length, epsilon);
}
static inline void
_sincosf (float angle,
float *out_s,
float *out_c)
{
#ifdef HAVE_SINCOSF
sincosf (angle, out_s, out_c);
#else
*out_s = sinf (angle);
*out_c = cosf (angle);
#endif
}
static void
align_points (const graphene_point_t *p,
const graphene_point_t *a,
const graphene_point_t *b,
graphene_point_t *q,
int n)
{
graphene_vec2_t n1;
float angle;
float s, c;
get_tangent (a, b, &n1);
angle = - atan2f (graphene_vec2_get_y (&n1), graphene_vec2_get_x (&n1));
_sincosf (angle, &s, &c);
for (int i = 0; i < n; i++)
{
q[i].x = (p[i].x - a->x) * c - (p[i].y - a->y) * s;
q[i].y = (p[i].x - a->x) * s + (p[i].y - a->y) * c;
}
}
static int
filter_allowable (float t[3],
int n)
{
float g[3];
int j = 0;
for (int i = 0; i < n; i++)
if (0 < t[i] && t[i] < 1)
g[j++] = t[i];
for (int i = 0; i < j; i++)
t[i] = g[i];
return j;
}
/* find solutions for at^2 + bt + c = 0 */
static int
solve_quadratic (float a, float b, float c, float t[2])
{
float d;
int n = 0;
if (fabsf (a) > 0.0001)
{
if (b*b > 4*a*c)
{
d = sqrtf (b*b - 4*a*c);
t[n++] = (-b + d)/(2*a);
t[n++] = (-b - d)/(2*a);
}
else
{
t[n++] = -b / (2*a);
}
}
else if (fabsf (b) > 0.0001)
{
t[n++] = -c / b;
}
return n;
}
int
gsk_curve_get_curvature_points (const GskCurve *curve,
float t[3])
{
const graphene_point_t *pts = curve->cubic.points;
graphene_point_t p[4];
float a, b, c, d;
float x, y, z;
int n;
if (curve->op != GSK_PATH_CUBIC)
return 0; /* FIXME */
align_points (pts, &pts[0], &pts[3], p, 4);
a = p[2].x * p[1].y;
b = p[3].x * p[1].y;
c = p[1].x * p[2].y;
d = p[3].x * p[2].y;
x = - 3*a + 2*b + 3*c - d;
y = 3*a - b - 3*c;
z = c - a;
n = solve_quadratic (x, y, z, t);
return filter_allowable (t, n);
}
/* Find cusps inside the open interval from 0 to 1.
*
* According to Stone & deRose, A Geometric Characterization
* of Parametric Cubic curves, a necessary and sufficient
* condition is that the first derivative vanishes.
*/
int
gsk_curve_get_cusps (const GskCurve *curve,
float t[2])
{
const graphene_point_t *pts = curve->cubic.points;
graphene_point_t p[3];
float ax, bx, cx;
float ay, by, cy;
float tx[3];
int nx;
int n = 0;
if (curve->op != GSK_PATH_CUBIC)
return 0;
p[0].x = 3 * (pts[1].x - pts[0].x);
p[0].y = 3 * (pts[1].y - pts[0].y);
p[1].x = 3 * (pts[2].x - pts[1].x);
p[1].y = 3 * (pts[2].y - pts[1].y);
p[2].x = 3 * (pts[3].x - pts[2].x);
p[2].y = 3 * (pts[3].y - pts[2].y);
ax = p[0].x - 2 * p[1].x + p[2].x;
bx = - 2 * p[0].x + 2 * p[1].x;
cx = p[0].x;
nx = solve_quadratic (ax, bx, cx, tx);
nx = filter_allowable (tx, nx);
ay = p[0].y - 2 * p[1].y + p[2].y;
by = - 2 * p[0].y + 2 * p[1].y;
cy = p[0].y;
for (int i = 0; i < nx; i++)
{
float ti = tx[i];
if (0 < ti && ti < 1 &&
fabsf (ay * ti * ti + by * ti + cy) < 0.001)
t[n++] = ti;
}
return n;
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

1115
gsk/gskcurveintersect.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -187,6 +187,19 @@ int gsk_curve_get_curvature_points (const GskCurve
int gsk_curve_get_cusps (const GskCurve *curve,
float t[2]);
int gsk_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
GskPathIntersection *kind,
int n);
int gsk_curve_self_intersect (const GskCurve *curve,
float *t1,
graphene_point_t *p,
int n);
G_END_DECLS

View File

@@ -17,6 +17,7 @@ static const GdkDebugKey gsk_debug_keys[] = {
{ "full-redraw", GSK_DEBUG_FULL_REDRAW, "Force full redraws" },
{ "sync", GSK_DEBUG_SYNC, "Sync after each frame" },
{ "staging", GSK_DEBUG_STAGING, "Use a staging image for texture upload (Vulkan only)" },
{ "no-glyphy", GSK_DEBUG_NO_GLYPHY, "Don't use GPU for glyph rendering (OpenGL only)", TRUE },
};
static guint gsk_debug_flags;

View File

@@ -19,7 +19,8 @@ typedef enum {
GSK_DEBUG_GEOMETRY = 1 << 9,
GSK_DEBUG_FULL_REDRAW = 1 << 10,
GSK_DEBUG_SYNC = 1 << 11,
GSK_DEBUG_STAGING = 1 << 12
GSK_DEBUG_STAGING = 1 << 12,
GSK_DEBUG_NO_GLYPHY = 1 << 13
} GskDebugFlags;
#define GSK_DEBUG_ANY ((1 << 13) - 1)

View File

@@ -144,6 +144,79 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func,
gpointer user_data);
/**
* GskPathIntersection:
* @GSK_PATH_INTERSECTION_NONE: No intersection
* @GSK_PATH_INTERSECTION_NORMAL: A normal intersection, where the two paths
* cross each other
* @GSK_PATH_INTERSECTION_START: The start of a segment where the two paths coincide
* @GSK_PATH_INTERSECTION_END: The end of a segment where the two paths coincide
*
* The values of this enumeration classify intersections
* between paths.
*/
typedef enum
{
GSK_PATH_INTERSECTION_NONE,
GSK_PATH_INTERSECTION_NORMAL,
GSK_PATH_INTERSECTION_START,
GSK_PATH_INTERSECTION_END,
} GskPathIntersection;
/**
* GskPathIntersectionFunc:
* @path1: the first path
* @point1: the intersection as point on @path1
* @path2: the second path
* @point2: the intersection as point on @path2
* @kind: specify the nature of the intersection
* @user_data: user data
*
* Prototype of the callback to iterate through the
* intersections of two paths.
*
* Returns: %TRUE to continue iterating, %FALSE to
* immediately abort and not call the function again
*
* Since: 4.14
*/
typedef gboolean (* GskPathIntersectionFunc) (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_foreach_intersection (GskPath *path1,
GskPath *path2,
GskPathIntersectionFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_union (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_intersection (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_difference (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_symmetric_difference (GskPath *first,
GskPath *second,
GskFillRule fill_rule);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_simplify (GskPath *self,
GskFillRule fill_rule);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS

705
gsk/gskpathintersect.c Normal file
View File

@@ -0,0 +1,705 @@
/*
* 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 "gskpathprivate.h"
#include "gskcurveprivate.h"
#include "gskpathbuilder.h"
#include "gskpathpoint.h"
#include "gskcontourprivate.h"
typedef struct
{
gsize count;
gboolean closed;
gboolean z_is_empty;
} CountCurveData;
static gboolean
count_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
CountCurveData *ccd = data;
(ccd->count)++;
if (op ==GSK_PATH_CLOSE)
{
ccd->closed = TRUE;
ccd->z_is_empty = graphene_point_equal (&pts[0], &pts[1]);
}
return TRUE;
}
static gsize
count_curves (const GskContour *contour,
gboolean *closed,
gboolean *z_is_empty)
{
CountCurveData data;
data.count = 0;
data.closed = FALSE;
data.z_is_empty = FALSE;
gsk_contour_foreach (contour, count_cb, &data);
*closed = data.closed;
*z_is_empty = data.z_is_empty;
return data.count;
}
typedef struct
{
GskPathPoint point1;
GskPathPoint point2;
GskPathIntersection kind;
} Intersection;
typedef struct
{
GskPath *path1;
GskPath *path2;
GskPathIntersectionFunc func;
gpointer data;
gsize contour1;
gsize contour2;
gsize idx1;
gsize idx2;
const GskContour *c1;
const GskContour *c2;
GskCurve curve1;
GskCurve curve2;
gboolean c1_closed;
gboolean c2_closed;
gboolean c1_z_is_empty;
gboolean c2_z_is_empty;
gsize c1_count;
gsize c2_count;
GArray *points;
GArray *all_points;
} PathIntersectData;
static gboolean
intersect_curve2 (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
PathIntersectData *pd = data;
float t1[10], t2[10];
graphene_point_t p[10];
GskPathIntersection kind[10];
int n;
if (op == GSK_PATH_MOVE)
{
if (gsk_contour_get_n_ops (pd->c2) == 1)
{
float dist, tt;
if (gsk_curve_get_closest_point (&pd->curve1, &pts[0], 1, &dist, &tt) && dist == 0)
{
Intersection is;
is.kind = GSK_PATH_INTERSECTION_NORMAL;
is.point1.contour = pd->contour1;
is.point1.idx = pd->idx1;
is.point1.t = tt;
is.point2.contour = pd->contour2;
is.point2.idx = 0;
is.point2.t = 1;
g_array_append_val (pd->points, is);
}
}
return TRUE;
}
if (op == GSK_PATH_CLOSE)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return TRUE;
}
pd->idx2++;
gsk_curve_init_foreach (&pd->curve2, op, pts, n_pts, weight);
n = gsk_curve_intersect (&pd->curve1, &pd->curve2, t1, t2, p, kind, 19);
for (int i = 0; i < n; i++)
{
Intersection is;
is.point1.contour = pd->contour1;
is.point2.contour = pd->contour2;
is.point1.idx = pd->idx1;
is.point2.idx = pd->idx2;
is.point1.t = t1[i];
is.point2.t = t2[i];
is.kind = kind[i];
#if 0
g_print ("append p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is.point1.contour, is.point1.idx, is.point1.t,
is.point2.contour, is.point2.idx, is.point2.t,
kn[is.kind]);
#endif
g_array_append_val (pd->points, is);
}
return TRUE;
}
static gboolean
intersect_curve (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
PathIntersectData *pd = data;
GskBoundingBox b1, b2;
if (op == GSK_PATH_MOVE)
{
if (gsk_contour_get_n_ops (pd->c1) == 1)
{
GskPathPoint point;
float dist;
if (gsk_contour_get_closest_point (pd->c2, &pts[0], 1, &point, &dist) && dist == 0)
{
Intersection is;
is.kind = GSK_PATH_INTERSECTION_NORMAL;
is.point1.contour = pd->contour1;
is.point1.idx = 0;
is.point1.t = 1;
is.point2.contour = pd->contour2;
is.point2.idx = point.idx;
is.point2.t = point.t;
g_array_append_val (pd->points, is);
}
}
return TRUE;
}
if (op == GSK_PATH_CLOSE)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return TRUE;
}
pd->idx1++;
gsk_curve_init_foreach (&pd->curve1, op, pts, n_pts, weight);
gsk_curve_get_bounds (&pd->curve1, &b1);
gsk_contour_get_bounds (pd->c2, &b2);
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
{
pd->idx2 = 0;
if (!gsk_contour_foreach (pd->c2, intersect_curve2, pd))
return FALSE;
}
return TRUE;
}
static gboolean
gsk_path_point_near (const GskPathPoint *p1,
const GskPathPoint *p2,
gboolean closed,
gsize count,
gboolean z_is_empty,
float epsilon)
{
if (p1->idx == p2->idx && fabsf (p1->t - p2->t) < epsilon)
return TRUE;
if (p1->idx + 1 == p2->idx && (1 - p1->t + p2->t < epsilon))
return TRUE;
if (p2->idx + 1 == p1->idx && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (closed)
{
if (p1->idx == 1 && p2->idx == count - 1 && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (p2->idx == 1 && p1->idx == count - 1 && (1 - p1->t + p2->t < epsilon))
return TRUE;
}
if (closed && z_is_empty)
{
if (p1->idx == 1 && p2->idx == count - 2 && (1 - p2->t + p1->t < epsilon))
return TRUE;
if (p2->idx == 1 && p1->idx == count - 2 && (1 - p1->t + p2->t < epsilon))
return TRUE;
}
return FALSE;
}
static int cmp_path1 (gconstpointer p1, gconstpointer p2);
static void
default_contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
pd->idx1 = 0;
g_array_set_size (pd->points, 0);
gsk_contour_foreach (contour1, intersect_curve, pd);
g_array_sort (pd->points, cmp_path1);
#if 0
g_print ("after sorting\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
for (gsize j = i + 1; j < pd->points->len; j++)
{
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
if (!gsk_path_point_near (&is1->point1, &is2->point1,
pd->c1_closed, pd->c1_count, pd->c1_z_is_empty,
0.001))
continue;
if (!gsk_path_point_near (&is1->point2, &is2->point2,
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
0.001))
continue;
if (is1->kind == GSK_PATH_INTERSECTION_NORMAL && is2->kind != GSK_PATH_INTERSECTION_NONE)
is1->kind = GSK_PATH_INTERSECTION_NONE;
else if (is2->kind == GSK_PATH_INTERSECTION_NORMAL && is1->kind != GSK_PATH_INTERSECTION_NONE)
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
}
#if 0
g_print ("after collapsing\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
for (gsize j = i + 1; j < pd->points->len; j++)
{
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
if (!gsk_path_point_near (&is1->point1, &is2->point1, FALSE, 0, FALSE, 0.001))
break;
if (!gsk_path_point_near (&is1->point2, &is2->point2,
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
0.001))
break;
if ((is1->kind == GSK_PATH_INTERSECTION_END &&
is2->kind == GSK_PATH_INTERSECTION_START) ||
(is1->kind == GSK_PATH_INTERSECTION_START &&
is2->kind == GSK_PATH_INTERSECTION_END))
{
is1->kind = GSK_PATH_INTERSECTION_NONE;
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
}
}
#if 0
g_print ("after merging segments\n");
for (gsize i = 0; i < pd->points->len; i++)
{
Intersection *is = &g_array_index (pd->points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize j = 0; j < pd->points->len; j++)
{
Intersection *is = &g_array_index (pd->points, Intersection, j);
if (is->kind != GSK_PATH_INTERSECTION_NONE)
g_array_append_val (pd->all_points, *is);
}
}
static int
circle_intersect (const graphene_point_t *center1,
float radius1,
const graphene_point_t *center2,
float radius2,
graphene_point_t points[2])
{
float d;
float a, h;
graphene_point_t m;
graphene_vec2_t n;
g_assert (radius1 >= 0);
g_assert (radius2 >= 0);
d = graphene_point_distance (center1, center2, NULL, NULL);
if (d < fabsf (radius1 - radius2))
return 0;
if (d > radius1 + radius2)
return 0;
if (d == radius1 + radius2)
{
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
return 1;
}
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
h = sqrtf (radius1*radius1 - a*a);
graphene_point_interpolate (center1, center2, a/d, &m);
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
graphene_vec2_normalize (&n, &n);
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
m.y + graphene_vec2_get_y (&n) * h);
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
m.y - graphene_vec2_get_y (&n) * h);
return 2;
}
static void
circle_contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
graphene_point_t center1, center2;
float radius1, radius2;
gboolean ccw1, ccw2;
graphene_point_t p[2];
int n;
Intersection is[2];
gsk_circle_contour_get_params (contour1, &center1, &radius1, &ccw1);
gsk_circle_contour_get_params (contour2, &center2, &radius2, &ccw2);
if (graphene_point_equal (&center1, &center2) && radius1 == radius2)
{
is[0].kind = GSK_PATH_INTERSECTION_START;
is[0].point1.contour = pd->contour1;
is[0].point1.idx = 1;
is[0].point1.t = 0;
is[0].point2.contour = pd->contour2;
is[0].point2.idx = 1;
is[0].point2.t = 0;
is[1].kind = GSK_PATH_INTERSECTION_END;
is[1].point1.contour = pd->contour1;
is[1].point1.idx = 1;
is[1].point1.t = 1;
is[1].point2.contour = pd->contour2;
is[1].point2.idx = 1;
is[1].point2.t = 1;
if (ccw1 != ccw2)
{
is[0].point2.t = 1;
is[1].point2.t = 0;
}
g_array_append_val (pd->all_points, is[0]);
g_array_append_val (pd->all_points, is[1]);
return;
}
n = circle_intersect (&center1, radius1, &center2, radius2, p);
for (int i = 0; i < n; i++)
{
float d;
is[i].kind = GSK_PATH_INTERSECTION_NORMAL;
is[i].point1.contour = pd->contour1;
is[i].point2.contour = pd->contour2;
gsk_contour_get_closest_point (contour1, &p[i], 1, &is[i].point1, &d);
gsk_contour_get_closest_point (contour2, &p[i], 1, &is[i].point2, &d);
}
if (n == 1)
{
g_array_append_val (pd->all_points, is[0]);
}
else if (n == 2)
{
if (gsk_path_point_compare (&is[0].point1, &is[1].point1) < 0)
{
g_array_append_val (pd->all_points, is[0]);
g_array_append_val (pd->all_points, is[1]);
}
else
{
g_array_append_val (pd->all_points, is[1]);
g_array_append_val (pd->all_points, is[0]);
}
}
}
static void
contour_collect_intersections (const GskContour *contour1,
const GskContour *contour2,
PathIntersectData *pd)
{
if (strcmp (gsk_contour_get_type_name (contour1), "GskCircleContour") == 0 &&
strcmp (gsk_contour_get_type_name (contour2), "GskCircleContour") == 0)
circle_contour_collect_intersections (contour1, contour2, pd);
else
default_contour_collect_intersections (contour1, contour2, pd);
}
static int
cmp_path1 (gconstpointer p1,
gconstpointer p2)
{
const Intersection *i1 = p1;
const Intersection *i2 = p2;
int i;
i = gsk_path_point_compare (&i1->point1, &i2->point1);
if (i != 0)
return i;
return gsk_path_point_compare (&i1->point2, &i2->point2);
}
static gboolean
contour_foreach_intersection (const GskContour *contour1,
PathIntersectData *pd)
{
GskBoundingBox b1, b2;
gsk_contour_get_bounds (contour1, &b1);
g_array_set_size (pd->all_points, 0);
for (gsize i = 0; i < gsk_path_get_n_contours (pd->path2); i++)
{
const GskContour *contour2 = gsk_path_get_contour (pd->path2, i);
gsk_contour_get_bounds (contour1, &b2);
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
{
pd->contour2 = i;
pd->c2 = contour2;
pd->c2_count = count_curves (contour2, &pd->c2_closed, &pd->c2_z_is_empty);
contour_collect_intersections (contour1, contour2, pd);
}
}
g_array_sort (pd->all_points, cmp_path1);
#if 0
g_print ("after sorting\n");
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
for (gsize i = 0; i + 1 < pd->all_points->len; i++)
{
Intersection *is1 = &g_array_index (pd->all_points, Intersection, i);
Intersection *is2 = &g_array_index (pd->all_points, Intersection, i + 1);
if (gsk_path_point_equal (&is1->point1, &is2->point1) &&
gsk_path_point_equal (&is1->point2, &is2->point2))
{
if (is1->kind == GSK_PATH_INTERSECTION_END &&
is2->kind == GSK_PATH_INTERSECTION_START)
{
is1->kind = GSK_PATH_INTERSECTION_NONE;
is2->kind = GSK_PATH_INTERSECTION_NONE;
}
else
{
is2->kind = MAX (is1->kind, is2->kind);
is1->kind = GSK_PATH_INTERSECTION_NONE;
}
}
}
g_print ("emitting\n");
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
is->point1.contour, is->point1.idx, is->point1.t,
is->point2.contour, is->point2.idx, is->point2.t,
kn[is->kind]);
}
#endif
for (gsize i = 0; i < pd->all_points->len; i++)
{
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
if (is->kind != GSK_PATH_INTERSECTION_NONE)
{
if (!pd->func (pd->path1, &is->point1, pd->path2, &is->point2, is->kind, pd->data))
return FALSE;
}
}
return TRUE;
}
/**
* gsk_path_foreach_intersection:
* @path1: the first path
* @path2: the second path
* @func: (scope call) (closure user_data): the function to call for intersections
* @user_data: (nullable): user data passed to @func
*
* Finds intersections between two paths.
*
* This function finds intersections between @path1 and @path2,
* and calls @func for each of them, in increasing order for @path1.
*
* When segments of the paths coincide, the callback is called once
* for the start of the segment, with @GSK_PATH_INTERSECTION_START, and
* once for the end of the segment, with @GSK_PATH_INTERSECTION_END.
* Note that other intersections may occur between the start and end
* of such a segment.
*
* If @func returns `FALSE`, the iteration is stopped.
*
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
*
* Since: 4.14
*/
gboolean
gsk_path_foreach_intersection (GskPath *path1,
GskPath *path2,
GskPathIntersectionFunc func,
gpointer data)
{
PathIntersectData pd = {
.path1 = path1,
.path2 = path2,
.func = func,
.data = data,
};
gboolean ret;
pd.points = g_array_new (FALSE, FALSE, sizeof (Intersection));
pd.all_points = g_array_new (FALSE, FALSE, sizeof (Intersection));
ret = TRUE;
for (gsize i = 0; i < gsk_path_get_n_contours (path1); i++)
{
const GskContour *contour1 = gsk_path_get_contour (path1, i);
pd.contour1 = i;
pd.c1 = contour1;
pd.c1_count = count_curves (contour1, &pd.c1_closed, &pd.c1_z_is_empty);
pd.idx1 = 0;
if (!contour_foreach_intersection (contour1, &pd))
{
ret = FALSE;
break;
}
}
g_array_unref (pd.points);
g_array_unref (pd.all_points);
return ret;
}
/* vim:set foldmethod=marker expandtab: */

1818
gsk/gskpathops.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -56,5 +56,19 @@ gboolean gsk_path_foreach_with_tolerance (GskPath
void gsk_path_builder_add_contour (GskPathBuilder *builder,
GskContour *contour);
typedef enum
{
GSK_PATH_OP_SIMPLIFY,
GSK_PATH_OP_UNION,
GSK_PATH_OP_INTERSECTION,
GSK_PATH_OP_DIFFERENCE,
GSK_PATH_OP_XOR
} GskPathOp;
GskPath * gsk_path_op (GskPathOp operation,
GskFillRule fill_rule,
GskPath *first,
GskPath *second);
G_END_DECLS

View File

@@ -20,6 +20,9 @@ gsk_private_gl_shaders = [
'gl/resources/custom.glsl',
'gl/resources/filled_border.glsl',
'gl/resources/mask.glsl',
'gl/resources/glyphy.atlas.glsl',
'gl/resources/glyphy.fs.glsl',
'gl/resources/glyphy.vs.glsl',
]
gsk_public_sources = files([
@@ -28,7 +31,9 @@ gsk_public_sources = files([
'gskglshader.c',
'gskpath.c',
'gskpathbuilder.c',
'gskpathintersect.c',
'gskpathmeasure.c',
'gskpathops.c',
'gskpathparser.c',
'gskpathpoint.c',
'gskrenderer.c',
@@ -45,6 +50,7 @@ gsk_private_sources = files([
'gskcairoblur.c',
'gskcontour.c',
'gskcurve.c',
'gskcurveintersect.c',
'gskdebug.c',
'gskprivate.c',
'gskprofiler.c',
@@ -54,6 +60,7 @@ gsk_private_sources = files([
'gl/gskglcompiler.c',
'gl/gskgldriver.c',
'gl/gskglglyphlibrary.c',
'gl/gskglglyphylibrary.c',
'gl/gskgliconlibrary.c',
'gl/gskglprogram.c',
'gl/gskglrenderjob.c',
@@ -194,6 +201,7 @@ gsk_deps = [
cairo_dep,
cairo_csi_dep,
libgdk_dep,
libglyphy_dep
]
libgsk_f16c = static_library('gsk_f16c',

View File

@@ -1017,6 +1017,7 @@ gtk_deps = [
epoxy_dep,
libm,
graphene_dep,
libglyphy_dep,
]
if x11_enabled

View File

@@ -14,7 +14,7 @@ project('gtk', 'c',
glib_req = '>= 2.76.0'
introspection_req = '>= 1.76.0' # keep this in sync with glib
pango_req = '>= 1.50.0' # keep this in sync with .gitlab-ci/test-msys.sh
harfbuzz_req = '>= 2.6.0'
harfbuzz_req = '>= 4.0.0'
fribidi_req = '>= 1.0.6'
cairo_req = '>= 1.14.0'
gdk_pixbuf_req = '>= 2.30.0'
@@ -26,6 +26,7 @@ cloudproviders_req = '>= 0.3.1'
xkbcommon_req = '>= 0.2.0'
sysprof_req = '>= 3.38.0'
vulkan_req = '>= 1.2'
libglyphy_req = '>= 0.2.0'
fs = import('fs')
gnome = import('gnome')
@@ -382,6 +383,9 @@ fribidi_dep = dependency('fribidi', version: fribidi_req,
default_options: ['docs=false'])
harfbuzz_dep = dependency('harfbuzz', version: harfbuzz_req,
default_options: ['coretext=enabled'])
libglyphy_dep = dependency('glyphy', version: libglyphy_req,
default_options: ['default_library=static', 'demo=disabled'],
fallback : ['glyphy', 'libglyphy_dep'])
# Require PangoFT2 if on X11 or wayland
pangoft_dep = dependency('pangoft2', version: pango_req,

6
subprojects/glyphy.wrap Normal file
View File

@@ -0,0 +1,6 @@
[wrap-git]
directory=glyphy
url=https://github.com/behdad/glyphy.git
revision=master
depth=1

123
tests/bigfont.c Normal file
View File

@@ -0,0 +1,123 @@
#include <gtk/gtk.h>
#define DEMO_TYPE_WIDGET (demo_widget_get_type ())
G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
struct _DemoWidget
{
GtkWidget parent_instance;
};
struct _DemoWidgetClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
static void
demo_widget_init (DemoWidget *self)
{
}
static void
demo_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
DemoWidget *self = DEMO_WIDGET (widget);
PangoLayout *layout;
int width, height;
int pwidth, pheight;
PangoFontDescription *desc;
int size;
double scale;
int x, y;
GdkRGBA color;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
gtk_widget_get_color (widget, &color);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "Best Aa");
pango_layout_get_pixel_size (layout, &pwidth, &pheight);
desc = pango_font_description_copy_static (pango_context_get_font_description (pango_layout_get_context (layout)));
size = pango_font_description_get_size (desc);
scale = MIN (width / (double)pwidth, height / (double)pheight);
pango_font_description_set_size (desc, size * scale * 0.5);
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
pango_layout_get_pixel_size (layout, &pwidth, &pheight);
x = floor ((width - pwidth) / 2);
y = floor ((height - pheight) / 2);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
gtk_snapshot_append_layout (snapshot, layout, &color);
gtk_snapshot_restore (snapshot);
g_object_unref (layout);
}
static void
demo_widget_dispose (GObject *object)
{
G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
}
static void
demo_widget_class_init (DemoWidgetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->dispose = demo_widget_dispose;
widget_class->snapshot = demo_widget_snapshot;
}
static GtkWidget *
demo_widget_new (void)
{
return g_object_new (DEMO_TYPE_WIDGET, NULL);
}
static const char css[] =
"* {\n"
" font-family: Cantarell;\n"
" font-weight: 520;\n"
"}";
int
main (int argc, char *argv[])
{
GtkCssProvider *style;
GtkWidget *window;
gtk_init ();
style = gtk_css_provider_new ();
gtk_css_provider_load_from_string (style, css);
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (style),
800);
window = gtk_window_new ();
gtk_window_set_child (GTK_WINDOW (window), demo_widget_new ());
gtk_window_present (GTK_WINDOW (window));
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
return 0;
}

View File

@@ -1,5 +1,7 @@
gtk_tests = [
# testname, optional extra sources
['movingtext'],
['bigfont'],
['testsections'],
['testfilelauncher'],
['input'],

210
tests/movingtext.c Normal file
View File

@@ -0,0 +1,210 @@
#include <gtk/gtk.h>
#define DEMO_TYPE_WIDGET (demo_widget_get_type ())
G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
struct _DemoWidget
{
GtkWidget parent_instance;
guint tick_cb;
guint64 start_time;
guint64 stop_time;
char *text;
float angle;
float size;
};
struct _DemoWidgetClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
static gboolean
tick_cb (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer user_data)
{
DemoWidget *self = DEMO_WIDGET (widget);
guint64 now;
now = gdk_frame_clock_get_frame_time (frame_clock);
if (self->start_time == 0)
self->start_time = now;
self->angle = 360 * (now - self->start_time) / (double)(G_TIME_SPAN_SECOND * 10);
self->size = 150 + 130 * sin (2 * G_PI * (now - self->start_time) / (double)(G_TIME_SPAN_SECOND * 5));
gtk_widget_queue_draw (widget);
return G_SOURCE_CONTINUE;
}
static gboolean
pressed_cb (GtkEventController *controller,
guint keyval,
guint keycode,
GdkModifierType state,
gpointer data)
{
DemoWidget *self = (DemoWidget *)gtk_event_controller_get_widget (controller);
if (keyval == GDK_KEY_space)
{
GdkFrameClock *frame_clock;
guint64 now;
frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
now = gdk_frame_clock_get_frame_time (frame_clock);
if (self->tick_cb)
{
gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_cb);
self->tick_cb = 0;
self->stop_time = now;
}
else
{
self->start_time += now - self->stop_time;
self->tick_cb = gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_cb, NULL, NULL);
}
}
return TRUE;
}
static void
demo_widget_init (DemoWidget *self)
{
GtkEventController *controller;
self->start_time = 0;
self->tick_cb = gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_cb, NULL, NULL);
controller = gtk_event_controller_key_new ();
g_signal_connect (controller, "key-pressed", G_CALLBACK (pressed_cb), NULL);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
}
static void
demo_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
DemoWidget *self = DEMO_WIDGET (widget);
PangoLayout *layout;
int width, height;
int pwidth, pheight;
PangoFontDescription *desc;
GdkRGBA color;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
gtk_widget_get_color (widget, &color);
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), self->text);
desc = pango_font_description_new ();
pango_font_description_set_family (desc, "Cantarell");
pango_font_description_set_weight (desc, 520);
pango_font_description_set_size (desc, self->size * PANGO_SCALE);
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
pango_layout_get_pixel_size (layout, &pwidth, &pheight);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0.5 * width, 0.5 * height));
gtk_snapshot_rotate (snapshot, self->angle);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- 0.5 * pwidth, - 0.5 * pheight));
gtk_snapshot_append_layout (snapshot, layout, &color);
gtk_snapshot_restore (snapshot);
g_object_unref (layout);
}
static void
demo_widget_dispose (GObject *object)
{
DemoWidget *self = DEMO_WIDGET (object);
g_free (self->text);
G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
}
static void
demo_widget_class_init (DemoWidgetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->dispose = demo_widget_dispose;
widget_class->snapshot = demo_widget_snapshot;
}
static GtkWidget *
demo_widget_new (const char *text,
gsize length)
{
DemoWidget *demo;
demo = g_object_new (DEMO_TYPE_WIDGET, NULL);
demo->text = g_strndup (text, length);
return GTK_WIDGET (demo);
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *demo;
char *text = NULL;
gsize length;
gtk_init ();
window = gtk_window_new ();
if (argc > 1)
{
GError *error = NULL;
if (!g_file_get_contents (argv[1], &text, &length, &error))
{
g_warning ("%s", error->message);
g_error_free (error);
text = NULL;
}
}
if (!text)
{
text = g_strdup ("Best Aa");
length = strlen (text);
}
demo = demo_widget_new (text, length);
gtk_window_set_child (GTK_WINDOW (window), demo);
g_free (text);
gtk_window_present (GTK_WINDOW (window));
gtk_widget_grab_focus (demo);
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
return 0;
}

View File

@@ -0,0 +1,551 @@
#include <gtk/gtk.h>
#include "gsk/gskcurveprivate.h"
static void
test_line_line_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 10, 0);
graphene_point_init (&p1[1], 10, 100);
graphene_point_init (&p2[0], 0, 10);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1, 0.1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2, 0.1, 0.0001);
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 10), 0.0001));
}
static void
test_line_line_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 10, 0);
graphene_point_init (&p1[1], 10, 100);
graphene_point_init (&p2[0], 10, 100);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1, 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2, 0, 0.0001);
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 100), 0.0001));
}
static void
test_line_line_none_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 10, 0);
graphene_point_init (&p2[0], 20, 0);
graphene_point_init (&p2[1], 30, 0);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 0);
graphene_point_init (&p1[0], 247.103424, 95.7965317);
graphene_point_init (&p1[1], 205.463974, 266.758484);
graphene_point_init (&p2[0], 183.735962, 355.968689);
graphene_point_init (&p2[1], 121.553253, 611.27655);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_cmpint (n, ==, 0);
}
static void
test_line_line_parallel (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1[2], t2[2];
graphene_point_t p[2];
GskPathIntersection kind[2];
int n;
graphene_point_init (&p1[0], 10, 10);
graphene_point_init (&p1[1], 110, 10);
graphene_point_init (&p2[0], 20, 10);
graphene_point_init (&p2[1], 120, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 2);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.1f, 0.01);
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[1], 0.9f, 0.01);
}
static void
test_line_line_same (void)
{
GskCurve c1, c2;
graphene_point_t p1[2], p2[2];
float t1[2], t2[2];
graphene_point_t p[2];
GskPathIntersection kind[2];
int n;
graphene_point_init (&p1[0], 10, 10);
graphene_point_init (&p1[1], 100, 10);
graphene_point_init (&p2[0], 10, 10);
graphene_point_init (&p2[1], 100, 10);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 2);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
g_assert_cmpfloat_with_epsilon (t2[1], 1.f, 0.01);
}
static void
test_line_curve_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
GskBoundingBox b;
graphene_point_init (&p1[0], 0, 100);
graphene_point_init (&p1[1], 50, 100);
graphene_point_init (&p1[2], 50, 0);
graphene_point_init (&p1[3], 100, 0);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 0.5, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.5, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (50, 50), 0.0001));
gsk_curve_get_tight_bounds (&c1, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
gsk_curve_get_tight_bounds (&c2, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
}
static void
test_line_curve_multiple_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
graphene_point_t pp;
int n;
GskBoundingBox b1, b2;
graphene_point_init (&p1[0], 100, 200);
graphene_point_init (&p1[1], 350, 100);
graphene_point_init (&p1[2], 100, 350);
graphene_point_init (&p1[3], 400, 300);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 0);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 200, 200);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.88487947, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[0]);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 280, 280);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.632056773, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[1], 0.499999911, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[1], 0.825892806, 0.0001);
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (231.25, 231.25), 0.001));
gsk_curve_get_point (&c1, t1[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_point (&c2, t2[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b1, &p[1]);
gsk_bounding_box_contains_point (&b2, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[1]);
graphene_point_init (&p2[0], 0, 0);
graphene_point_init (&p2[1], 1000, 1000);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 3);
g_assert_cmpint (n, ==, 3);
g_assert_cmpfloat_with_epsilon (t1[0], 0.863803446, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0.305377066, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (305.377075, 305.377075), 0.001));
gsk_curve_get_point (&c1, t1[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
gsk_curve_get_point (&c2, t2[0], &pp);
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[1], 0.136196628, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[1], 0.176975891, 0.0001);
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
gsk_curve_get_point (&c1, t1[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
gsk_curve_get_point (&c2, t2[1], &pp);
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
g_assert_cmpfloat_with_epsilon (t1[2], 0.5, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[2], 0.231249988, 0.0001);
g_assert_true (graphene_point_near (&p[2], &GRAPHENE_POINT_INIT (231.249985, 231.249985), 0.001));
gsk_curve_get_point (&c1, t1[2], &pp);
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
gsk_curve_get_point (&c2, t2[2], &pp);
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
gsk_curve_get_tight_bounds (&c1, &b1);
gsk_curve_get_tight_bounds (&c2, &b2);
gsk_bounding_box_contains_point (&b1, &p[0]);
gsk_bounding_box_contains_point (&b1, &p[1]);
gsk_bounding_box_contains_point (&b1, &p[2]);
gsk_bounding_box_contains_point (&b2, &p[0]);
gsk_bounding_box_contains_point (&b2, &p[1]);
gsk_bounding_box_contains_point (&b2, &p[2]);
}
static void
test_line_curve_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 0, 100);
graphene_point_init (&p1[1], 50, 100);
graphene_point_init (&p1[2], 50, 0);
graphene_point_init (&p1[3], 100, 0);
graphene_point_init (&p2[0], 100, 0);
graphene_point_init (&p2[1], 100, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (100, 0), 0.0001));
}
static void
test_line_curve_none_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[2];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 333, 78);
graphene_point_init (&p1[1], 415, 78);
graphene_point_init (&p1[2], 463, 131);
graphene_point_init (&p1[3], 463, 223);
graphene_point_init (&p2[0], 520, 476);
graphene_point_init (&p2[1], 502, 418);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 1);
g_assert_cmpint (n, ==, 0);
}
static void
test_curve_curve_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
GskBoundingBox b;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 33.333, 100);
graphene_point_init (&p1[2], 66.667, 0);
graphene_point_init (&p1[3], 100, 100);
graphene_point_init (&p2[0], 0, 50);
graphene_point_init (&p2[1], 100, 0);
graphene_point_init (&p2[2], 20, 0); // weight 20
graphene_point_init (&p2[3], 50, 100);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 2);
g_assert_cmpfloat (t1[0], <, 0.5);
g_assert_cmpfloat (t1[1], >, 0.5);
g_assert_cmpfloat (t2[0], <, 0.5);
g_assert_cmpfloat (t2[1], >, 0.5);
gsk_curve_get_tight_bounds (&c1, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
gsk_curve_get_tight_bounds (&c2, &b);
gsk_bounding_box_contains_point (&b, &p[0]);
}
static void
test_curve_curve_end_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 0, 0);
graphene_point_init (&p1[1], 33.333, 100);
graphene_point_init (&p1[2], 66.667, 0);
graphene_point_init (&p1[3], 100, 100);
graphene_point_init (&p2[0], 100, 100);
graphene_point_init (&p2[1], 100, 0);
graphene_point_init (&p2[2], 20, 0);
graphene_point_init (&p2[3], 10, 0);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 1);
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
}
static void
test_curve_curve_end_intersection2 (void)
{
GskCurve c, c1, c2;
graphene_point_t p1[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 200, 100);
graphene_point_init (&p1[1], 300, 300);
graphene_point_init (&p1[2], 100, 300);
graphene_point_init (&p1[3], 300, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_split (&c, 0.5, &c1, &c2);
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 2);
}
static void
test_curve_curve_max_intersection (void)
{
GskCurve c1, c2;
graphene_point_t p1[4], p2[4];
float t1[9], t2[9];
graphene_point_t p[9];
GskPathIntersection kind[9];
int n;
graphene_point_init (&p1[0], 106, 100);
graphene_point_init (&p1[1], 118, 264);
graphene_point_init (&p1[2], 129, 4);
graphene_point_init (&p1[3], 128, 182);
graphene_point_init (&p2[0], 54, 135);
graphene_point_init (&p2[1], 263, 136);
graphene_point_init (&p2[2], 2, 143);
graphene_point_init (&p2[3], 141, 150);
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CUBIC, p1));
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CUBIC, p2));
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 9);
g_assert_cmpint (n, ==, 9);
}
/* This showed up as artifacts in the stroker when our
* intersection code failed to find intersections with
* horizontal lines
*/
static void
test_curve_intersection_horizontal_line (void)
{
GskCurve c1, c2;
float t1, t2;
graphene_point_t p;
GskPathIntersection kind;
int n;
gsk_curve_init (&c1,
gsk_pathop_encode (GSK_PATH_CONIC,
(const graphene_point_t[4]) {
GRAPHENE_POINT_INIT (200.000, 165.000),
GRAPHENE_POINT_INIT (220.858, 165.000),
GRAPHENE_POINT_INIT (1.4142, 0),
GRAPHENE_POINT_INIT (292.929, 92.929),
}));
gsk_curve_init_foreach (&c2,
GSK_PATH_LINE,
(const graphene_point_t[2]) {
GRAPHENE_POINT_INIT (300, 110),
GRAPHENE_POINT_INIT (100, 110),
},
2,
0);
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, &kind, 1);
g_assert_true (n == 1);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_func ("/curve/intersection/line-line", test_line_line_intersection);
g_test_add_func ("/curve/intersection/line-line-none", test_line_line_none_intersection);
g_test_add_func ("/curve/intersection/line-line-end", test_line_line_end_intersection);
g_test_add_func ("/curve/intersection/line-line-parallel", test_line_line_parallel);
g_test_add_func ("/curve/intersection/line-line-same", test_line_line_same);
g_test_add_func ("/curve/intersection/line-curve", test_line_curve_intersection);
g_test_add_func ("/curve/intersection/line-curve-end", test_line_curve_end_intersection);
g_test_add_func ("/curve/intersection/line-curve-none", test_line_curve_none_intersection);
g_test_add_func ("/curve/intersection/line-curve-multiple", test_line_curve_multiple_intersection);
g_test_add_func ("/curve/intersection/curve-curve", test_curve_curve_intersection);
g_test_add_func ("/curve/intersection/curve-curve-end", test_curve_curve_end_intersection);
g_test_add_func ("/curve/intersection/curve-curve-end2", test_curve_curve_end_intersection2);
g_test_add_func ("/curve/intersection/curve-curve-max", test_curve_curve_max_intersection);
g_test_add_func ("/curve/intersection/horizontal-line", test_curve_intersection_horizontal_line);
return g_test_run ();
}

View File

@@ -374,6 +374,8 @@ tests = [
['shader'],
['path', [ 'path-utils.c' ] ],
['path-special-cases'],
['path-intersect'],
['path-ops', [ 'path-utils.c' ] ],
]
test_cargs = []
@@ -406,6 +408,7 @@ endforeach
internal_tests = [
[ 'curve' ],
[ 'curve-special-cases' ],
[ 'curve-intersect' ],
[ 'path-private' ],
[ 'diff' ],
[ 'half-float' ],

View File

@@ -0,0 +1,806 @@
#include <gtk/gtk.h>
#define assert_path_point_equal(point,_contour,_idx,_t) \
g_assert_cmpint ((point)->contour, ==, (_contour)); \
g_assert_cmpint ((point)->idx, ==, (_idx)); \
g_assert_cmpfloat_with_epsilon ((point)->t, (_t), 0.0001);
typedef struct
{
GskPathPoint point1[20];
GskPathPoint point2[20];
GskPathIntersection kind[20];
int found;
} CollectData;
static gboolean
collect_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
CollectData *res = data;
#if 0
const char *kn[] = { "none", "normal", "start", "end" };
g_print ("%s idx1 %lu t1 %f idx2 %lu t2 %f\n",
kn[kind],
point1->idx, point1->t, point2->idx, point2->t);
#endif
g_assert_true (res->found < 20);
res->point1[res->found] = *point1;
res->point2[res->found] = *point2;
res->kind[res->found] = kind;
res->found++;
return TRUE;
}
static void
test_intersect_simple (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 150 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0.75);
assert_path_point_equal (&res.point2[1], 0, 4, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_simple2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_simple3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 300 100 h -200 v 100 h 200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_reverse (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 150 v 100 h 200 v -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
assert_path_point_equal (&res.point2[0], 0, 4, 0.25);
assert_path_point_equal (&res.point2[1], 0, 1, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_line_box (void)
{
GskPath *path1, *path2;
graphene_point_t p1, p2;
CollectData res;
path1 = gsk_path_parse ("M 50 150 l 300 0");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 50.f/300.f);
assert_path_point_equal (&res.point1[1], 0, 1, 250.f/300.f);
assert_path_point_equal (&res.point2[0], 0, 4, 0.5);
assert_path_point_equal (&res.point2[1], 0, 2, 0.5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_point_get_position (&res.point1[0], path1, &p1);
gsk_path_point_get_position (&res.point2[0], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_point_get_position (&res.point1[1], path1, &p1);
gsk_path_point_get_position (&res.point2[1], path2, &p2);
g_assert_true (graphene_point_equal (&p1, &p2));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_xplus (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 0 L 100 100 M 0 100 L 100 0");
path2 = gsk_path_parse ("M 0 50 L 100 50 M 50 0 L 50 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
assert_path_point_equal (&res.point1[2], 1, 1, 0.5);
assert_path_point_equal (&res.point1[3], 1, 1, 0.5);
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
assert_path_point_equal (&res.point2[1], 1, 1, 0.5);
assert_path_point_equal (&res.point2[2], 0, 1, 0.5);
assert_path_point_equal (&res.point2[3], 1, 1, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_point (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 50");
path2 = gsk_path_parse ("M 0 0 L 0 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 0, 1);
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
res.found = 0;
gsk_path_foreach_intersection (path2, path1, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
assert_path_point_equal (&res.point2[0], 0, 0, 1);
res.found = 0;
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
g_assert_true (res.found == 1);
assert_path_point_equal (&res.point1[0], 0, 0, 1);
assert_path_point_equal (&res.point2[0], 0, 0, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 100 L 200 100");
path2 = gsk_path_parse ("M 150 0 150 200 M 50 0 50 200");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
assert_path_point_equal (&res.point1[1], 0, 1, 0.75f);
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[1], 0, 1, 0.5f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 0 100 L 200 100");
path2 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
assert_path_point_equal (&res.point1[1], 0, 1, 0.3f);
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[1], 2, 1, 0.f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_NORMAL);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_contours3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
path2 = gsk_path_parse ("M 0 100 L 200 100");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
assert_path_point_equal (&res.point1[0], 0, 1, 0.5f);
assert_path_point_equal (&res.point1[1], 1, 1, 0.5f);
assert_path_point_equal (&res.point2[0], 0, 1, 0.75f);
assert_path_point_equal (&res.point2[1], 0, 1, 0.25f);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 1, 1);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide2 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 1);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 1, 0.75);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide3 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -50 h -50 v 50 h -25 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 5, 1);
assert_path_point_equal (&res.point2[2], 0, 5, 0);
assert_path_point_equal (&res.point2[3], 0, 1, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide4 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -100 h -50 v 100 h -25 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 4);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_NORMAL);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[0], 0, 1, 0);
assert_path_point_equal (&res.point2[1], 0, 6, 0.5);
assert_path_point_equal (&res.point2[2], 0, 4, 0.5);
assert_path_point_equal (&res.point2[3], 0, 1, 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
/* the next few tests explore overlapping segments */
static void
test_intersect_coincide5 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 100 v 100 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 25 v -50 h -100 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
assert_path_point_equal (&res.point1[2], 0, 1, 0.75);
assert_path_point_equal (&res.point1[3], 0, 1, 1);
assert_path_point_equal (&res.point1[4], 0, 2, 0.5);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 5, 0);
assert_path_point_equal (&res.point2[2], 0, 5, 1);
assert_path_point_equal (&res.point2[3], 0, 1, 0.75);
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_coincide6 (void)
{
GskPath *path1, *path2;
CollectData res;
path1 = gsk_path_parse ("M 150 100 h 75 l 25 50 v 50 h -100 z");
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 50 v -50 h -125 z");
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 5);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
assert_path_point_equal (&res.point1[0], 0, 1, 0);
assert_path_point_equal (&res.point1[1], 0, 1, 2./3.);
assert_path_point_equal (&res.point1[2], 0, 1, 1);
assert_path_point_equal (&res.point1[3], 0, 1, 1);
assert_path_point_equal (&res.point1[4], 0, 3, 0);
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
assert_path_point_equal (&res.point2[1], 0, 5, 0);
assert_path_point_equal (&res.point2[2], 0, 1, 0.625);
assert_path_point_equal (&res.point2[3], 0, 5, 0.5);
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static int
circle_intersect (const graphene_point_t *center1,
float radius1,
const graphene_point_t *center2,
float radius2,
graphene_point_t points[2])
{
float d;
float a, h;
graphene_point_t m;
graphene_vec2_t n;
g_assert (radius1 >= 0);
g_assert (radius2 >= 0);
d = graphene_point_distance (center1, center2, NULL, NULL);
if (d < fabsf (radius1 - radius2))
return 0;
if (d > radius1 + radius2)
return 0;
if (d == radius1 + radius2)
{
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
return 1;
}
/*
a + b = d;
a^2 + h^2 = r1^2
b^2 + h^2 = r2^2
a^2 - (d - a)^2 = r1^2 - r2^2
a^2 - (d^2 - 2ad + a2) = r1^2 - r2^2
2ad -d^2 = r1^2 - r2^2
2ad = r1^2 - r2^2 + d^2
a = (r1^2 - r2^2 + d^2) / (2d)
p1/2 = c1 + a/d * (c2 - c1) +/- h * n(c1,c2);
*/
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
h = sqrtf (radius1*radius1 - a*a);
graphene_point_interpolate (center1, center2, a/d, &m);
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
graphene_vec2_normalize (&n, &n);
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
m.y + graphene_vec2_get_y (&n) * h);
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
m.y - graphene_vec2_get_y (&n) * h);
return 2;
}
static void
test_intersect_circle (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (1, 1), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 0);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle2 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 25), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 0);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle3 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 22), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 1);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle4 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
graphene_point_t p[2], pos;
int n;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 18), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
&GRAPHENE_POINT_INIT (0, 18), 10,
p);
g_assert_true (n == 2);
gsk_path_point_get_position (&res.point1[0], path1, &pos);
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
gsk_path_point_get_position (&res.point1[1], path1, &pos);
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle5 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
graphene_point_t p[2], pos;
int n;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (10, 10), 10);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
&GRAPHENE_POINT_INIT (10, 10), 10,
p);
g_assert_true (n == 2);
gsk_path_point_get_position (&res.point1[0], path1, &pos);
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
gsk_path_point_get_position (&res.point1[1], path1, &pos);
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle6 (void)
{
GskPathBuilder *builder;
GskPath *path1;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
path1 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
}
static void
test_intersect_circle7 (void)
{
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_reverse_path (builder, path1);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 2);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
static void
test_intersect_circle_rounded_rect (void)
{
GskRoundedRect rr;
GskPathBuilder *builder;
GskPath *path1, *path2;
CollectData res;
rr.bounds = GRAPHENE_RECT_INIT (10, 10, 100, 100);
rr.corner[GSK_CORNER_TOP_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_TOP_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_BOTTOM_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
rr.corner[GSK_CORNER_BOTTOM_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &rr);
path1 = gsk_path_builder_free_to_path (builder);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 30), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 30), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 90), 20);
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 90), 20);
path2 = gsk_path_builder_free_to_path (builder);
res.found = 0;
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
g_assert_true (res.found == 8);
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[5] == GSK_PATH_INTERSECTION_END);
g_assert_true (res.kind[6] == GSK_PATH_INTERSECTION_START);
g_assert_true (res.kind[7] == GSK_PATH_INTERSECTION_END);
gsk_path_unref (path1);
gsk_path_unref (path2);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_func ("/path/intersect/simple", test_intersect_simple);
g_test_add_func ("/path/intersect/simple2", test_intersect_simple2);
g_test_add_func ("/path/intersect/simple3", test_intersect_simple3);
g_test_add_func ("/path/intersect/reverse", test_intersect_reverse);
g_test_add_func ("/path/intersect/line-box", test_intersect_line_box);
g_test_add_func ("/path/intersect/xplus", test_intersect_xplus);
g_test_add_func ("/path/intersect/point", test_intersect_point);
g_test_add_func ("/path/intersect/contours", test_intersect_contours);
g_test_add_func ("/path/intersect/contours2", test_intersect_contours2);
g_test_add_func ("/path/intersect/contours3", test_intersect_contours3);
g_test_add_func ("/path/intersect/coincide", test_intersect_coincide);
g_test_add_func ("/path/intersect/coincide2", test_intersect_coincide2);
g_test_add_func ("/path/intersect/coincide3", test_intersect_coincide3);
g_test_add_func ("/path/intersect/coincide4", test_intersect_coincide4);
g_test_add_func ("/path/intersect/coincide5", test_intersect_coincide5);
g_test_add_func ("/path/intersect/coincide6", test_intersect_coincide6);
g_test_add_func ("/path/intersect/circle", test_intersect_circle);
g_test_add_func ("/path/intersect/circle2", test_intersect_circle2);
g_test_add_func ("/path/intersect/circle3", test_intersect_circle3);
g_test_add_func ("/path/intersect/circle4", test_intersect_circle4);
g_test_add_func ("/path/intersect/circle5", test_intersect_circle5);
g_test_add_func ("/path/intersect/circle6", test_intersect_circle6);
g_test_add_func ("/path/intersect/circle7", test_intersect_circle7);
g_test_add_func ("/path/intersect/circle-rounded-rect", test_intersect_circle_rounded_rect);
return g_test_run ();
}

381
testsuite/gsk/path-ops.c Normal file
View File

@@ -0,0 +1,381 @@
/*
* Copyright © 2022 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include <gtk/gtk.h>
#include "path-utils.h"
typedef enum
{
OP_UNION,
OP_INTERSECTION,
OP_DIFFERENCE,
OP_SYMMETRIC_DIFFERENCE,
} Op;
static void
test_ops_simple (void)
{
struct {
const char *in1;
const char *in2;
Op op;
const char *out;
} tests[] = {
/* partially overlapping edge */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_UNION,
"M 100 100 L 100 200 L 150 200 L 150 250 L 250 250 L 200 200 L 150 150 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_INTERSECTION,
"M 150 200 L 200 200 L 150 150 L 150 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 150 200 L 150 150 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 150 150 L 150 250 L 250 250 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 150 200 L 150 150 L 100 100 Z M 200 200 L 150 200 L 150 250 "
"L 250 250 L 200 200 Z" },
/* two triangles in general position */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_UNION,
"M 100 100 L 100 200 L 123.33333587646484 200 L 100 240 L 170 240 L 170 200 L 200 200 "
"L 170 170 L 170 120 L 151.57894897460938 151.57894897460938 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_INTERSECTION,
"M 123.33333587646484 200 L 170 200 L 170 170 L 151.57894897460938 151.57894897460938 "
"L 123.33332824707031 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 123.33333587646484 200 L 151.57894897460938 151.57894897460938 "
"L 100 100 Z M 170 200 L 200 200 L 170 170 L 170 200 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 170 120 L 100 240 L 170 240 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 123.33333587646484 200 L 151.57894897460938 151.57894897460938 "
"L 100 100 Z M 170 200 L 123.33333587646484 200 L 100 240 L 170 240 L 170 200 Z "
"M 170 200 L 200 200 L 170 170 L 170 200 Z M 151.57894897460938 151.57894897460938 "
"L 170 170 L 170 120 L 151.57894897460938 151.57894897460938 Z" },
/* nested contours, oriented in opposite direction */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_UNION,
"M 100 100 L 100 200 L 200 200 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_INTERSECTION,
"M 170 190 L 120 140 L 120 190 L 170 190 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 140 L 170 190 L 120 190 L 120 140 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 170 190 L 120 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 140 L 170 190 L 120 190 L 120 140 Z" },
/* nested contours, oriented in opposite direction, other way around */
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_UNION,
"M 200 200 L 100 100 L 100 200 L 200 200 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_INTERSECTION,
"M 120 140 L 120 190 L 170 190 L 120 140 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_DIFFERENCE,
"M 200 200 L 100 100 L 100 200 L 200 200 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
{ "M 100 100 L 200 200 L 100 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 200 200 L 100 100 L 100 200 L 200 200 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
/* nested contours, oriented in the same direction */
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_UNION,
"M 100 100 L 100 200 L 200 200 L 100 100 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_INTERSECTION,
"M 120 140 L 120 190 L 170 190 L 120 140 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
{ "M 100 100 L 100 200 L 200 200 Z",
"M 120 140 L 120 190 L 170 190 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 200 200 L 100 100 Z M 120 190 L 120 140 L 170 190 L 120 190 Z" },
/* a 3-way intersection */
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_UNION,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z M 100 108.57099914550781 "
"L 147.61927795410156 108.57099914550781 L 200 108.57099914550781 L 200 50 "
"L 100 50 L 100 108.57099914550781 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_INTERSECTION,
"M 147.61904907226562 108.57142639160156 L 150 104 L 145 104 "
"L 147.61904907226562 108.57142639160156 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_DIFFERENCE,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z" },
{ "M 100 200 L 150 104 L 145 104 L 200 200 Z",
"M 100 108.571 L 200 108.571 L 200 50 L 100 50 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 147.61904907226562 108.57142639160156 L 100 200 L 200 200 "
"L 147.61904907226562 108.57142639160156 Z M 150 104 "
"L 147.61904907226562 108.57142639160156 L 200 108.57099914550781 "
"L 200 50 L 100 50 L 100 108.57099914550781 L 147.61927795410156 108.57099914550781 "
"L 145 104 L 150 104 Z" },
/* touching quadratics */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 200 Q 150 100 200 200 Z",
OP_UNION,
"M 100 100 "
"Q 124.987984 149.975967, 149.975967 149.999985 "
"Q 174.987976 150.024033, 200 100 "
"L 100 100 "
"Z "
"M 149.975967 150 "
"Q 124.987984 150.024033, 100 200 "
"L 200 200 "
"Q 174.987976 149.975967, 149.975967 150.000015 "
"Z" },
/* overlapping quadratics, two intersections, different orientations */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_UNION,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180313 152.360611, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_INTERSECTION,
"M 127.638626 139.99939 "
"Q 149.999619 160.000275, 172.360611 140.000061 "
"Q 150 120.000061, 127.639389 139.999939 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150 120.000061, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 Q 150 80 200 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150 120.000061, 172.360611 139.999939 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z "
"M 172.360611 140.000061 "
"Q 149.999619 160.000275, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180313 152.360611, 172.360611 139.999939 "
"Z" },
/* overlapping quadratics, two intersections, same orientation */
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_UNION,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180695 152.361374, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_INTERSECTION,
"M 127.638626 139.99939 "
"Q 149.999619 160.000275, 172.360611 140.000061 "
"Q 150.000397 119.999725, 127.639397 139.999939 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150.000397 119.999725, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z" },
{ "M 100 100 Q 150 200 200 100 Z",
"M 100 180 L 200 180 Q 150 80 100 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 "
"Q 113.819313 127.638626, 127.638626 139.999374 "
"Q 150.000397 119.999725, 172.361389 140.000626 "
"Q 186.180298 127.639389, 200 100 "
"L 100 100 "
"Z "
"M 172.360611 140.000061 "
"Q 149.999619 160.000275, 127.638626 139.999374 "
"Q 113.819695 152.360611, 100 180 "
"L 200 180 "
"Q 186.180695 152.361374, 172.361389 140.000626 "
"Z" },
/* two polygons with near edges */
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_UNION,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 100 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_INTERSECTION,
"M 250 100 L 150 103 L 250 180 L 300 103 L 250 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_DIFFERENCE,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 300 103 L 250 180 L 150 103 L 250 100 L 100 100 Z" },
{ "M 100 100 L 100 200 L 400 200 L 400 100 Z",
"M 150 103 L 250 100 L 300 103 L 250 180 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 100 100 L 100 200 L 400 200 L 400 100 L 250 100 L 300 103 L 250 180 L 150 103 L 250 100 L 100 100 Z" },
/* Collinear line segments */
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_UNION,
"M 150 100 "
"L 100 100 "
"L 100 200 "
"L 200 133.333328 "
"L 300 200 "
"L 300 100 "
"L 250 100 "
"L 200 100 "
"L 150 100 "
"Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_INTERSECTION,
"M 200 100 "
"L 150 100 "
"L 200 133.333328 "
"L 250 100 "
"L 200 100 "
"Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_DIFFERENCE,
"M 150 100 L 100 100 L 100 200 L 200 133.33332824707031 L 150 100 Z" },
{ "M 100 100 L 200 100 L 250 100 L 100 200 Z",
"M 150 100 L 300 100 L 300 200 Z",
OP_SYMMETRIC_DIFFERENCE,
"M 150 100 L 100 100 L 100 200 L 200 133.33332824707031 L 150 100 Z "
"M 250 100 L 200 133.33332824707031 L 300 200 L 300 100 L 250 100 Z" },
/* a complicated union */
{ "M 175 100 L 175 400 L 300 400 L 300 100 z",
"M 100 100 C 200 200 200 300 100 400 L 0 400 C 233.3333334 300 233.3333334 200 0 100 Z",
OP_UNION,
"M 175 100 "
"L 175 250 "
"L 175 400 "
"L 300 400 "
"L 300 100 "
"L 175 100 "
"Z "
"M 175 250 "
"Q 175 175, 100 100 "
"L 0 100 "
"Q 174.955811 174.981064, 174.999985 249.962112 "
"Z "
"M 100 400 "
"Q 175 325, 175 250 "
"Q 175.044189 324.981049, 0 400 "
"L 100 400 "
"Z" },
};
for (int i = 0; i < G_N_ELEMENTS (tests); i++)
{
GskPath *p1, *p2, *p3, *p;
if (g_test_verbose ())
{
const char *opname[] = { "union", "intersection", "difference", "symmetric-difference" };
g_test_message ("testcase %d op %s \"%s\" \"%s\"", i, opname[tests[i].op], tests[i].in1, tests[i].in2);
}
p1 = gsk_path_parse (tests[i].in1);
p2 = gsk_path_parse (tests[i].in2);
switch (tests[i].op)
{
case OP_UNION:
p = gsk_path_union (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_INTERSECTION:
p = gsk_path_intersection (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_DIFFERENCE:
p = gsk_path_difference (p1, p2, GSK_FILL_RULE_WINDING);
break;
case OP_SYMMETRIC_DIFFERENCE:
p = gsk_path_symmetric_difference (p1, p2, GSK_FILL_RULE_WINDING);
break;
default:
g_assert_not_reached ();
}
g_assert_nonnull (p);
p3 = gsk_path_parse (tests[i].out);
assert_path_equal_with_epsilon (p, p3, 0.0001);
gsk_path_unref (p);
gsk_path_unref (p1);
gsk_path_unref (p2);
gsk_path_unref (p3);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/ops/simple", test_ops_simple);
return g_test_run ();
}

View File

@@ -0,0 +1,104 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib 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 GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
void
do_pathop (const char *op, int *argc, const char ***argv)
{
GError *error = NULL;
const char *fill = "winding";
char **args = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule"), N_("RULE") },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH…") },
{ NULL, },
};
GskPath *path1, *path2, *result;
GskFillRule fill_rule;
char *prgname;
char *summary;
prgname = g_strconcat ("gtk4-path-tool ", op, NULL);
summary = g_strdup_printf (_("Apply the %s path operation."), op);
g_set_prgname (prgname);
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, summary);
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No paths given."));
exit (1);
}
path1 = get_path (args[0]);
if (args[1] != NULL)
path2 = get_path (args[1]);
else
path2 = NULL;
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
if (strcmp (op, "simplify") == 0)
result = gsk_path_simplify (path1, fill_rule);
else if (strcmp (op, "union") == 0)
result = gsk_path_union (path1, path2, fill_rule);
else if (strcmp (op, "intersection") == 0)
result = gsk_path_intersection (path1, path2, fill_rule);
else if (strcmp (op, "difference") == 0)
result = gsk_path_difference (path1, path2, fill_rule);
else if (strcmp (op, "symmetric-difference") == 0)
result = gsk_path_symmetric_difference (path1, path2, fill_rule);
else
{
char *msg = g_strdup_printf (_("'%s' is not a supported operation."), op);
g_printerr ("%s\n", msg);
exit (1);
}
if (result)
{
char *str = gsk_path_to_string (result);
g_print ("%s\n", str);
g_free (str);
}
else
{
g_printerr ("%s\n", _("That didn't work out."));
exit (1);
}
}

View File

@@ -32,12 +32,14 @@
#include "path-view.h"
static void
show_path_fill (GskPath *path,
show_path_fill (GskPath *path1,
GskPath *path2,
GskFillRule fill_rule,
const GdkRGBA *fg_color,
const GdkRGBA *bg_color,
gboolean show_points,
gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color)
{
GtkWidget *window, *sw, *child;
@@ -52,14 +54,17 @@ show_path_fill (GskPath *path,
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path);
child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", TRUE,
"fill-rule", fill_rule,
"fg-color", fg_color,
"bg-color", bg_color,
"show-points", show_points,
"show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color,
NULL);
@@ -74,12 +79,14 @@ show_path_fill (GskPath *path,
}
static void
show_path_stroke (GskPath *path,
show_path_stroke (GskPath *path1,
GskPath *path2,
GskStroke *stroke,
const GdkRGBA *fg_color,
const GdkRGBA *bg_color,
gboolean show_points,
gboolean show_controls,
gboolean show_intersections,
const GdkRGBA *point_color)
{
GtkWidget *window, *sw, *child;
@@ -94,14 +101,17 @@ show_path_stroke (GskPath *path,
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path);
child = g_object_new (PATH_TYPE_VIEW, NULL);
g_object_set (child,
"path1", path1,
"path2", path2,
"do-fill", FALSE,
"stroke", stroke,
"fg-color", fg_color,
"bg-color", bg_color,
"show-points", show_points,
"show-controls", show_controls,
"show-intersections", show_intersections,
"point-color", point_color,
NULL);
@@ -123,6 +133,7 @@ do_show (int *argc,
gboolean do_stroke = FALSE;
gboolean show_points = FALSE;
gboolean show_controls = FALSE;
gboolean show_intersections = FALSE;
const char *fill = "winding";
const char *fg_color = "black";
const char *bg_color = "white";
@@ -141,6 +152,7 @@ do_show (int *argc,
{ "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path"), NULL },
{ "points", 0, 0, G_OPTION_ARG_NONE, &show_points, N_("Show path points"), NULL },
{ "controls", 0, 0, G_OPTION_ARG_NONE, &show_controls, N_("Show control points"), NULL },
{ "intersections", 0, 0, G_OPTION_ARG_NONE, &show_intersections, N_("Show intersections"), NULL },
{ "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") },
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") },
{ "point-color", 0, 0, G_OPTION_ARG_STRING, &point_color, N_("Point color"), N_("COLOR") },
@@ -160,7 +172,8 @@ do_show (int *argc,
{ "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") },
{ NULL, }
};
GskPath *path;
GskPath *path1;
GskPath *path2;
GskFillRule fill_rule;
GdkRGBA fg, bg, pc;
GskLineCap line_cap;
@@ -210,13 +223,17 @@ do_show (int *argc,
exit (1);
}
if (g_strv_length (args) > 1)
if (g_strv_length (args) > 2)
{
g_printerr ("%s\n", _("Can only show a single path"));
g_printerr ("%s\n", _("Can only show one or two paths"));
exit (1);
}
path = get_path (args[0]);
path1 = get_path (args[0]);
if (g_strv_length (args) > 1)
path2 = get_path (args[1]);
else
path2 = NULL;
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
get_color (&fg, fg_color);
@@ -234,11 +251,12 @@ do_show (int *argc,
_gsk_stroke_set_dashes (stroke, dashes);
if (do_stroke)
show_path_stroke (path, stroke, &fg, &bg, show_points, show_controls, &pc);
show_path_stroke (path1, path2, stroke, &fg, &bg, show_points, show_controls, show_intersections, &pc);
else
show_path_fill (path, fill_rule, &fg, &bg, show_points, show_controls, &pc);
show_path_fill (path1, path2, fill_rule, &fg, &bg, show_points, show_controls, show_intersections, &pc);
gsk_path_unref (path);
g_clear_pointer (&path1, gsk_path_unref);
g_clear_pointer (&path2, gsk_path_unref);
g_strfreev (args);
}

View File

@@ -38,6 +38,12 @@ usage (void)
"Perform various tasks on paths.\n"
"\n"
"Commands:\n"
" simplify Simplify the path\n"
" intersection Intersect two paths\n"
" union Create the union of two paths\n"
" difference Create the difference of two paths\n"
" symmetric-difference\n"
" Create the symmetric difference of two paths\n"
" decompose Decompose the path\n"
" reverse Reverse the path\n"
" restrict Restrict the path to a segment\n"
@@ -128,8 +134,12 @@ main (int argc, const char *argv[])
if (strcmp (argv[0], "decompose") == 0)
do_decompose (&argc, &argv);
else if (strcmp (argv[0], "difference") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "info") == 0)
do_info (&argc, &argv);
else if (strcmp (argv[0], "intersection") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "render") == 0)
do_render (&argc, &argv);
else if (strcmp (argv[0], "restrict") == 0)
@@ -138,6 +148,12 @@ main (int argc, const char *argv[])
do_reverse (&argc, &argv);
else if (strcmp (argv[0], "show") == 0)
do_show (&argc, &argv);
else if (strcmp (argv[0], "simplify") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "symmetric-difference") == 0)
do_pathop (argv[0], &argc, &argv);
else if (strcmp (argv[0], "union") == 0)
do_pathop (argv[0], &argc, &argv);
else
usage ();

View File

@@ -6,6 +6,7 @@ void do_restrict (int *argc, const char ***argv);
void do_reverse (int *argc, const char ***argv);
void do_render (int *argc, const char ***argv);
void do_show (int *argc, const char ***argv);
void do_pathop (const char *op, int *argc, const char ***argv);
GskPath *get_path (const char *arg);
int get_enum_value (GType type,

View File

@@ -26,6 +26,7 @@ gtk_tools = [
['gtk4-path-tool', ['gtk-path-tool.c',
'gtk-path-tool-decompose.c',
'gtk-path-tool-info.c',
'gtk-path-tool-pathops.c',
'gtk-path-tool-render.c',
'gtk-path-tool-restrict.c',
'gtk-path-tool-reverse.c',

View File

@@ -25,6 +25,8 @@ struct _PathView
{
GtkWidget parent_instance;
GskPath *path1;
GskPath *path2;
GskPath *path;
GskStroke *stroke;
graphene_rect_t bounds;
@@ -35,13 +37,17 @@ struct _PathView
gboolean do_fill;
gboolean show_points;
gboolean show_controls;
gboolean show_intersections;
GskPath *line_path;
GskPath *point_path;
GdkRGBA point_color;
GskPath *intersection_line_path;
GskPath *intersection_point_path;
};
enum {
PROP_PATH = 1,
PROP_PATH1 = 1,
PROP_PATH2,
PROP_DO_FILL,
PROP_STROKE,
PROP_FILL_RULE,
@@ -50,6 +56,7 @@ enum {
PROP_POINT_COLOR,
PROP_SHOW_POINTS,
PROP_SHOW_CONTROLS,
PROP_SHOW_INTERSECTIONS,
N_PROPERTIES
};
@@ -79,10 +86,14 @@ path_view_dispose (GObject *object)
{
PathView *self = PATH_VIEW (object);
g_clear_pointer (&self->path1, gsk_path_unref);
g_clear_pointer (&self->path2, gsk_path_unref);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->stroke, gsk_stroke_free);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->point_path, gsk_path_unref);
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
G_OBJECT_CLASS (path_view_parent_class)->dispose (object);
}
@@ -97,8 +108,12 @@ path_view_get_property (GObject *object,
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
case PROP_PATH1:
g_value_set_boxed (value, self->path1);
break;
case PROP_PATH2:
g_value_set_boxed (value, self->path2);
break;
case PROP_DO_FILL:
@@ -129,6 +144,10 @@ path_view_get_property (GObject *object,
g_value_set_boolean (value, self->show_controls);
break;
case PROP_SHOW_INTERSECTIONS:
g_value_set_boolean (value, self->show_intersections);
break;
case PROP_POINT_COLOR:
g_value_set_boxed (value, &self->point_color);
break;
@@ -268,6 +287,93 @@ update_controls (PathView *self)
update_bounds (self);
}
typedef struct {
GskPathBuilder *line_builder;
GskPathBuilder *point_builder;
GskPathPoint start;
int segment;
} IntersectionData;
static gboolean
intersection_cb (GskPath *path1,
const GskPathPoint *point1,
GskPath *path2,
const GskPathPoint *point2,
GskPathIntersection kind,
gpointer data)
{
IntersectionData *id = data;
graphene_point_t pos;
switch (kind)
{
case GSK_PATH_INTERSECTION_NORMAL:
gsk_path_point_get_position (point1, path1, &pos);
gsk_path_builder_add_circle (id->point_builder, &pos, 3);
break;
case GSK_PATH_INTERSECTION_START:
if (id->segment == 0)
id->start = *point1;
id->segment++;
break;
case GSK_PATH_INTERSECTION_END:
id->segment--;
if (id->segment == 0)
gsk_path_builder_add_segment (id->line_builder, path1, &id->start, point1);
break;
case GSK_PATH_INTERSECTION_NONE:
default:
g_assert_not_reached ();
}
return TRUE;
}
static void
update_intersections (PathView *self)
{
IntersectionData id;
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
if (!self->show_intersections || !self->path1 || !self->path2)
return;
id.line_builder = gsk_path_builder_new ();
id.point_builder = gsk_path_builder_new ();
id.segment = 0;
gsk_path_foreach_intersection (self->path1, self->path2, intersection_cb, &id);
self->intersection_line_path = gsk_path_builder_free_to_path (id.line_builder);
self->intersection_point_path = gsk_path_builder_free_to_path (id.point_builder);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
update_path (PathView *self)
{
GskPathBuilder *builder;
g_clear_pointer (&self->path, gsk_path_unref);
builder = gsk_path_builder_new ();
if (self->path1)
gsk_path_builder_add_path (builder, self->path1);
if (self->path2)
gsk_path_builder_add_path (builder, self->path2);
self->path = gsk_path_builder_free_to_path (builder);
update_intersections (self);
update_controls (self);
}
static void
path_view_set_property (GObject *object,
guint prop_id,
@@ -278,11 +384,16 @@ path_view_set_property (GObject *object,
switch (prop_id)
{
case PROP_PATH1:
g_clear_pointer (&self->path1, gsk_path_unref);
self->path1 = g_value_dup_boxed (value);
update_path (self);
break;
case PROP_PATH:
g_clear_pointer (&self->path, gsk_path_unref);
self->path = g_value_dup_boxed (value);
update_controls (self);
case PROP_PATH2:
g_clear_pointer (&self->path2, gsk_path_unref);
self->path2 = g_value_dup_boxed (value);
update_path (self);
break;
case PROP_DO_FILL:
@@ -321,6 +432,11 @@ path_view_set_property (GObject *object,
update_controls (self);
break;
case PROP_SHOW_INTERSECTIONS:
self->show_intersections = g_value_get_boolean (value);
update_intersections (self);
break;
case PROP_POINT_COLOR:
self->point_color = *(GdkRGBA *) g_value_get_boxed (value);
gtk_widget_queue_draw (GTK_WIDGET (self));
@@ -344,9 +460,9 @@ path_view_measure (GtkWidget *widget,
PathView *self = PATH_VIEW (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = (int) ceilf (self->bounds.size.width) + 2 * self->padding;
*minimum = *natural = (int) ceilf (self->bounds.origin.x + self->bounds.size.width) + 2 * self->padding;
else
*minimum = *natural = (int) ceilf (self->bounds.size.height) + 2 * self->padding;
*minimum = *natural = (int) ceilf (self->bounds.origin.y + self->bounds.size.height) + 2 * self->padding;
}
static void
@@ -383,6 +499,18 @@ path_view_snapshot (GtkWidget *widget,
gtk_snapshot_append_stroke (snapshot, self->point_path, stroke, &self->fg);
}
if (self->intersection_line_path)
{
GskStroke *stroke = gsk_stroke_new (gsk_stroke_get_line_width (self->stroke));
gtk_snapshot_append_stroke (snapshot, self->intersection_line_path, stroke, &self->point_color);
}
if (self->intersection_point_path)
{
gtk_snapshot_append_fill (snapshot, self->intersection_point_path, GSK_FILL_RULE_WINDING, &self->point_color);
}
gtk_snapshot_restore (snapshot);
}
@@ -399,8 +527,13 @@ path_view_class_init (PathViewClass *class)
widget_class->measure = path_view_measure;
widget_class->snapshot = path_view_snapshot;
properties[PROP_PATH]
= g_param_spec_boxed ("path", NULL, NULL,
properties[PROP_PATH1]
= g_param_spec_boxed ("path1", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_PATH2]
= g_param_spec_boxed ("path2", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
@@ -440,6 +573,11 @@ path_view_class_init (PathViewClass *class)
FALSE,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_SHOW_INTERSECTIONS]
= g_param_spec_boolean ("show-intersections", NULL, NULL,
FALSE,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_POINT_COLOR]
= g_param_spec_boxed ("point-color", NULL, NULL,
GDK_TYPE_RGBA,
@@ -452,6 +590,6 @@ GtkWidget *
path_view_new (GskPath *path)
{
return g_object_new (PATH_TYPE_VIEW,
"path", path,
"path1", path,
NULL);
}