Compare commits
31 Commits
wip/matthi
...
glyphy-no-
Author | SHA1 | Date | |
---|---|---|---|
|
568ae041d8 | ||
|
d5a6cface7 | ||
|
6c9c9ad6c3 | ||
|
de08441d4d | ||
|
8a438c46ae | ||
|
5fb1a572c6 | ||
|
1ab45331c0 | ||
|
23e36c9245 | ||
|
0beb416b53 | ||
|
3d27f67107 | ||
|
9574e49df6 | ||
|
0347e4cd76 | ||
|
14a8321b02 | ||
|
5bb4846442 | ||
|
4715afc822 | ||
|
9f58740486 | ||
|
7088acb88b | ||
|
732a607127 | ||
|
e8ef875a7b | ||
|
56b17715f1 | ||
|
6775a22cff | ||
|
380ac5804d | ||
|
241c6811f9 | ||
|
ba442fc24e | ||
|
9d5c23f342 | ||
|
568ac6b7f6 | ||
|
a94cc20067 | ||
|
638a91451a | ||
|
769679d111 | ||
|
306f524e59 | ||
|
e86281d81c |
@@ -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);
|
||||
|
@@ -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
544
gsk/gl/gskglglyphylibrary.c
Normal 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;
|
||||
}
|
142
gsk/gl/gskglglyphylibraryprivate.h
Normal file
142
gsk/gl/gskglglyphylibraryprivate.h
Normal 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__ */
|
@@ -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))
|
||||
|
@@ -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);
|
||||
|
@@ -34,10 +34,14 @@
|
||||
#include <gsk/gskroundedrectprivate.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"
|
||||
@@ -149,6 +153,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.
|
||||
*/
|
||||
@@ -2959,10 +2966,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);
|
||||
@@ -3098,6 +3105,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)
|
||||
@@ -4463,6 +4727,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)
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
15
gsk/gl/resources/glyphy.atlas.glsl
Normal file
15
gsk/gl/resources/glyphy.atlas.glsl
Normal 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);
|
||||
}
|
62
gsk/gl/resources/glyphy.fs.glsl
Normal file
62
gsk/gl/resources/glyphy.fs.glsl
Normal 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);
|
||||
}
|
25
gsk/gl/resources/glyphy.vs.glsl
Normal file
25
gsk/gl/resources/glyphy.vs.glsl
Normal 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);
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
#extension GL_OES_standard_derivatives : enable
|
||||
|
||||
#ifndef GSK_LEGACY
|
||||
precision highp float;
|
||||
#endif
|
||||
|
100
gsk/gskboundingboxprivate.h
Normal file
100
gsk/gskboundingboxprivate.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _GskBoundingBox GskBoundingBox;
|
||||
|
||||
struct _GskBoundingBox {
|
||||
graphene_point_t min;
|
||||
graphene_point_t max;
|
||||
};
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init (GskBoundingBox *self,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b)
|
||||
{
|
||||
self->min.x = MIN (a->x, b->x);
|
||||
self->min.y = MIN (a->y, b->y);
|
||||
self->max.x = MAX (a->x, b->x);
|
||||
self->max.y = MAX (a->y, b->y);
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init_copy (GskBoundingBox *self,
|
||||
const GskBoundingBox *src)
|
||||
{
|
||||
self->min = src->min;
|
||||
self->max = src->max;
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline GskBoundingBox *
|
||||
gsk_bounding_box_init_from_rect (GskBoundingBox *self,
|
||||
const graphene_rect_t *bounds)
|
||||
{
|
||||
self->min = bounds->origin;
|
||||
self->max.x = bounds->origin.x + bounds->size.width;
|
||||
self->max.y = bounds->origin.y + bounds->size.height;
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_bounding_box_expand (GskBoundingBox *self,
|
||||
const graphene_point_t *p)
|
||||
{
|
||||
self->min.x = MIN (self->min.x, p->x);
|
||||
self->min.y = MIN (self->min.y, p->y);
|
||||
self->max.x = MAX (self->max.x, p->x);
|
||||
self->max.y = MAX (self->max.y, p->y);
|
||||
}
|
||||
|
||||
static inline graphene_rect_t *
|
||||
gsk_bounding_box_to_rect (const GskBoundingBox *self,
|
||||
graphene_rect_t *rect)
|
||||
{
|
||||
rect->origin = self->min;
|
||||
rect->size.width = self->max.x - self->min.x;
|
||||
rect->size.height = self->max.y - self->min.y;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_contains_point (const GskBoundingBox *self,
|
||||
const graphene_point_t *p)
|
||||
{
|
||||
return self->min.x <= p->x && p->x <= self->max.x &&
|
||||
self->min.y <= p->y && p->y <= self->max.y;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_contains_point_with_epsilon (const GskBoundingBox *self,
|
||||
const graphene_point_t *p,
|
||||
float epsilon)
|
||||
{
|
||||
return self->min.x - epsilon <= p->x && p->x <= self->max.x + epsilon &&
|
||||
self->min.y - epsilon <= p->y && p->y <= self->max.y + epsilon;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_bounding_box_intersection (const GskBoundingBox *a,
|
||||
const GskBoundingBox *b,
|
||||
GskBoundingBox *res)
|
||||
{
|
||||
graphene_point_t min, max;
|
||||
|
||||
min.x = MAX (a->min.x, b->min.x);
|
||||
min.y = MAX (a->min.y, b->min.y);
|
||||
max.x = MIN (a->max.x, b->max.x);
|
||||
max.y = MIN (a->max.y, b->max.y);
|
||||
|
||||
if (res)
|
||||
gsk_bounding_box_init (res, &min, &max);
|
||||
|
||||
return min.x <= max.x && min.y <= max.y;
|
||||
}
|
||||
|
||||
G_END_DECLS
|
2436
gsk/gskcontour.c
Normal file
2436
gsk/gskcontour.c
Normal file
File diff suppressed because it is too large
Load Diff
103
gsk/gskcontourprivate.h
Normal file
103
gsk/gskcontourprivate.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FLAT,
|
||||
GSK_PATH_CLOSED
|
||||
} GskPathFlags;
|
||||
|
||||
typedef struct _GskContour GskContour;
|
||||
|
||||
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
|
||||
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);
|
||||
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
|
||||
float radius,
|
||||
float start_angle,
|
||||
float end_angle);
|
||||
GskContour * gsk_standard_contour_new (GskPathFlags flags,
|
||||
const graphene_point_t *points,
|
||||
gsize n_points,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
gssize offset);
|
||||
|
||||
void gsk_contour_copy (GskContour * dest,
|
||||
const GskContour *src);
|
||||
GskContour * gsk_contour_dup (const GskContour *src);
|
||||
GskContour * gsk_contour_reverse (const GskContour *src);
|
||||
|
||||
gsize gsk_contour_get_size (const GskContour *self);
|
||||
GskPathFlags gsk_contour_get_flags (const GskContour *self);
|
||||
void gsk_contour_print (const GskContour *self,
|
||||
GString *string);
|
||||
gboolean gsk_contour_get_bounds (const GskContour *self,
|
||||
graphene_rect_t *bounds);
|
||||
gpointer gsk_contour_init_measure (const GskContour *self,
|
||||
float tolerance,
|
||||
float *out_length);
|
||||
void gsk_contour_free_measure (const GskContour *self,
|
||||
gpointer data);
|
||||
gboolean gsk_contour_foreach (const GskContour *self,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
void gsk_contour_get_start_end (const GskContour *self,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end);
|
||||
void gsk_contour_get_point (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
float distance,
|
||||
GskPathDirection direction,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
gboolean gsk_contour_get_closest_point (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent);
|
||||
int gsk_contour_get_winding (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
const graphene_point_t *point);
|
||||
void gsk_contour_add_segment (const GskContour *self,
|
||||
GskPathBuilder *builder,
|
||||
gpointer measure_data,
|
||||
gboolean emit_move_to,
|
||||
float start,
|
||||
float end);
|
||||
float gsk_contour_get_curvature (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
float distance,
|
||||
graphene_point_t *center);
|
||||
|
||||
|
||||
G_END_DECLS
|
2034
gsk/gskcurve.c
Normal file
2034
gsk/gskcurve.c
Normal file
File diff suppressed because it is too large
Load Diff
879
gsk/gskcurveintersect.c
Normal file
879
gsk/gskcurveintersect.c
Normal file
@@ -0,0 +1,879 @@
|
||||
/*
|
||||
* Copyright © 2020 Red Hat, Inc
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Matthias Clasen <mclasen@redhat.com>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "gskcurveprivate.h"
|
||||
|
||||
/* {{{ Utilities */
|
||||
|
||||
static void
|
||||
get_tangent (const graphene_point_t *p0,
|
||||
const graphene_point_t *p1,
|
||||
graphene_vec2_t *t)
|
||||
{
|
||||
graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
|
||||
graphene_vec2_normalize (t, t);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
acceptable (float t)
|
||||
{
|
||||
return 0 - FLT_EPSILON <= t && t <= 1 + FLT_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 = - atan2 (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 void
|
||||
find_point_on_line (const graphene_point_t *p1,
|
||||
const graphene_point_t *p2,
|
||||
const graphene_point_t *q,
|
||||
float *t)
|
||||
{
|
||||
if (p2->x != p1->x)
|
||||
*t = (q->x - p1->x) / (p2->x - p1->x);
|
||||
else
|
||||
*t = (q->y - p1->y) / (p2->y - p1->y);
|
||||
}
|
||||
|
||||
/* 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 (fabs (a) > 0.0001)
|
||||
{
|
||||
if (b*b > 4*a*c)
|
||||
{
|
||||
d = sqrt (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 (fabs (b) > 0.0001)
|
||||
{
|
||||
t[n++] = -c / b;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Solve P = 0 where P is
|
||||
* P = (1-t)^2*pa + 2*t*(1-t)*pb + t^2*pc
|
||||
*/
|
||||
static int
|
||||
get_quadratic_roots (float pa, float pb, float pc, float roots[2])
|
||||
{
|
||||
float a, b, c, d;
|
||||
int n_roots = 0;
|
||||
|
||||
a = pa - 2 * pb + pc;
|
||||
b = 2 * (pb - pa);
|
||||
c = pa;
|
||||
|
||||
d = b*b - 4*a*c;
|
||||
|
||||
if (d > 0.0001)
|
||||
{
|
||||
float q = sqrt (d);
|
||||
roots[n_roots] = (-b + q) / (2 * a);
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
roots[n_roots] = (-b - q) / (2 * a);
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
}
|
||||
else if (fabs (d) < 0.0001)
|
||||
{
|
||||
roots[n_roots] = -b / (2 * a);
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
}
|
||||
|
||||
return n_roots;
|
||||
}
|
||||
|
||||
static float
|
||||
cuberoot (float v)
|
||||
{
|
||||
if (v < 0)
|
||||
return -pow (-v, 1.f / 3);
|
||||
return pow (v, 1.f / 3);
|
||||
}
|
||||
|
||||
/* Solve P = 0 where P is
|
||||
* P = (1-t)^3*pa + 3*t*(1-t)^2*pb + 3*t^2*(1-t)*pc + t^3*pd
|
||||
*/
|
||||
static int
|
||||
get_cubic_roots (float pa, float pb, float pc, float pd, float roots[3])
|
||||
{
|
||||
float a, b, c, d;
|
||||
float q, q2;
|
||||
float p, p3;
|
||||
float discriminant;
|
||||
float u1, v1, sd;
|
||||
int n_roots = 0;
|
||||
|
||||
d = -pa + 3*pb - 3*pc + pd;
|
||||
a = 3*pa - 6*pb + 3*pc;
|
||||
b = -3*pa + 3*pb;
|
||||
c = pa;
|
||||
|
||||
if (fabs (d) < 0.0001)
|
||||
{
|
||||
if (fabs (a) < 0.0001)
|
||||
{
|
||||
if (fabs (b) < 0.0001)
|
||||
return 0;
|
||||
|
||||
if (acceptable (-c / b))
|
||||
{
|
||||
roots[0] = -c / b;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
q = sqrt (b*b - 4*a*c);
|
||||
roots[n_roots] = (-b + q) / (2 * a);
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
|
||||
roots[n_roots] = (-b - q) / (2 * a);
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
|
||||
return n_roots;
|
||||
}
|
||||
|
||||
a /= d;
|
||||
b /= d;
|
||||
c /= d;
|
||||
|
||||
p = (3*b - a*a)/3;
|
||||
p3 = p/3;
|
||||
q = (2*a*a*a - 9*a*b + 27*c)/27;
|
||||
q2 = q/2;
|
||||
discriminant = q2*q2 + p3*p3*p3;
|
||||
|
||||
if (discriminant < 0)
|
||||
{
|
||||
float mp3 = -p/3;
|
||||
float mp33 = mp3*mp3*mp3;
|
||||
float r = sqrt (mp33);
|
||||
float t = -q / (2*r);
|
||||
float cosphi = t < -1 ? -1 : (t > 1 ? 1 : t);
|
||||
float phi = acos (cosphi);
|
||||
float crtr = cuberoot (r);
|
||||
float t1 = 2*crtr;
|
||||
|
||||
roots[n_roots] = t1 * cos (phi/3) - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
roots[n_roots] = t1 * cos ((phi + 2*M_PI) / 3) - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
roots[n_roots] = t1 * cos ((phi + 4*M_PI) / 3) - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
|
||||
return n_roots;
|
||||
}
|
||||
|
||||
if (discriminant == 0)
|
||||
{
|
||||
u1 = q2 < 0 ? cuberoot (-q2) : -cuberoot (q2);
|
||||
roots[n_roots] = 2*u1 - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
roots[n_roots] = -u1 - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
|
||||
return n_roots;
|
||||
}
|
||||
|
||||
sd = sqrt (discriminant);
|
||||
u1 = cuberoot (sd - q2);
|
||||
v1 = cuberoot (sd + q2);
|
||||
roots[n_roots] = u1 - v1 - a/3;
|
||||
if (acceptable (roots[n_roots]))
|
||||
n_roots++;
|
||||
|
||||
return n_roots;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Cusps and inflections */
|
||||
|
||||
/* Get the points where the curvature of curve is
|
||||
* zero, or a maximum or minimum, inside the open
|
||||
* interval from 0 to 1.
|
||||
*/
|
||||
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;
|
||||
|
||||
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 &&
|
||||
fabs (ay * ti * ti + by * ti + cy) < 0.001)
|
||||
t[n++] = ti;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Intersection */
|
||||
|
||||
static int
|
||||
line_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
const graphene_point_t *pts1 = curve1->line.points;
|
||||
const graphene_point_t *pts2 = curve2->line.points;
|
||||
float a1 = pts1[0].x - pts1[1].x;
|
||||
float b1 = pts1[0].y - pts1[1].y;
|
||||
float a2 = pts2[0].x - pts2[1].x;
|
||||
float b2 = pts2[0].y - pts2[1].y;
|
||||
float det = a1 * b2 - b1 * a2;
|
||||
|
||||
if (fabs(det) > 0.01)
|
||||
{
|
||||
float tt = ((pts1[0].x - pts2[0].x) * b2 - (pts1[0].y - pts2[0].y) * a2) / det;
|
||||
float ss = - ((pts1[0].y - pts2[0].y) * a1 - (pts1[0].x - pts2[0].x) * b1) / det;
|
||||
|
||||
if (acceptable (tt) && acceptable (ss))
|
||||
{
|
||||
p[0].x = pts1[0].x + tt * (pts1[1].x - pts1[0].x);
|
||||
p[0].y = pts1[0].y + tt * (pts1[1].y - pts1[0].y);
|
||||
|
||||
t1[0] = tt;
|
||||
t2[0] = ss;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else /* parallel lines */
|
||||
{
|
||||
float r = a1 * (pts1[1].y - pts2[0].y) - (pts1[1].x - pts2[0].x) * b1;
|
||||
float dist = (r * r) / (a1 * a1 + b1 * b1);
|
||||
float t, s, tt, ss;
|
||||
|
||||
if (dist > 0.01)
|
||||
return 0;
|
||||
|
||||
if (pts1[1].x != pts1[0].x)
|
||||
{
|
||||
t = (pts2[0].x - pts1[0].x) / (pts1[1].x - pts1[0].x);
|
||||
s = (pts2[1].x - pts1[0].x) / (pts1[1].x - pts1[0].x);
|
||||
}
|
||||
else
|
||||
{
|
||||
t = (pts2[0].y - pts1[0].y) / (pts1[1].y - pts1[0].y);
|
||||
s = (pts2[1].y - pts1[0].y) / (pts1[1].y - pts1[0].y);
|
||||
}
|
||||
|
||||
if ((t < 0 && s < 0) || (t > 1 && s > 1))
|
||||
return 0;
|
||||
|
||||
if (acceptable (t))
|
||||
{
|
||||
t1[0] = t;
|
||||
t2[0] = 0;
|
||||
p[0] = pts2[0];
|
||||
}
|
||||
else if (t < 0)
|
||||
{
|
||||
if (pts2[1].x != pts2[0].x)
|
||||
tt = (pts1[0].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
|
||||
else
|
||||
tt = (pts1[0].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
|
||||
|
||||
t1[0] = 0;
|
||||
t2[0] = tt;
|
||||
p[0] = pts1[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pts2[1].x != pts2[0].x)
|
||||
tt = (pts1[1].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
|
||||
else
|
||||
tt = (pts1[1].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
|
||||
|
||||
t1[0] = 1;
|
||||
t2[0] = tt;
|
||||
p[0] = pts1[1];
|
||||
}
|
||||
|
||||
if (acceptable (s))
|
||||
{
|
||||
if (t2[0] == 1)
|
||||
return 1;
|
||||
|
||||
t1[1] = s;
|
||||
t2[1] = 1;
|
||||
p[1] = pts2[1];
|
||||
}
|
||||
else if (s < 0)
|
||||
{
|
||||
if (t1[0] == 0)
|
||||
return 1;
|
||||
|
||||
if (pts2[1].x != pts2[0].x)
|
||||
ss = (pts1[0].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
|
||||
else
|
||||
ss = (pts1[0].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
|
||||
|
||||
t1[1] = 0;
|
||||
t2[1] = ss;
|
||||
p[1] = pts1[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t1[0] == 1)
|
||||
return 1;
|
||||
|
||||
if (pts2[1].x != pts2[0].x)
|
||||
ss = (pts1[1].x - pts2[0].x) / (pts2[1].x - pts2[0].x);
|
||||
else
|
||||
ss = (pts1[1].y - pts2[0].y) / (pts2[1].y - pts2[0].y);
|
||||
|
||||
t1[1] = 1;
|
||||
t2[1] = ss;
|
||||
p[1] = pts1[1];
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
line_quad_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
const graphene_point_t *a = &curve1->line.points[0];
|
||||
const graphene_point_t *b = &curve1->line.points[1];
|
||||
graphene_point_t pts[4];
|
||||
float t[2];
|
||||
int m, i, j;
|
||||
|
||||
/* Rotate things to place curve1 on the x axis,
|
||||
* then solve curve2 for y == 0.
|
||||
*/
|
||||
align_points (curve2->quad.points, a, b, pts, 3);
|
||||
|
||||
m = get_quadratic_roots (pts[0].y, pts[1].y, pts[2].y, t);
|
||||
|
||||
j = 0;
|
||||
for (i = 0; i < m; i++)
|
||||
{
|
||||
t2[j] = t[i];
|
||||
gsk_curve_get_point (curve2, t2[j], &p[j]);
|
||||
find_point_on_line (a, b, &p[j], &t1[j]);
|
||||
if (acceptable (t1[j]))
|
||||
j++;
|
||||
if (j == n)
|
||||
break;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
static int
|
||||
line_cubic_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
const graphene_point_t *a = &curve1->line.points[0];
|
||||
const graphene_point_t *b = &curve1->line.points[1];
|
||||
graphene_point_t pts[4];
|
||||
float t[3];
|
||||
int m, i, j;
|
||||
|
||||
/* Rotate things to place curve1 on the x axis,
|
||||
* then solve curve2 for y == 0.
|
||||
*/
|
||||
align_points (curve2->cubic.points, a, b, pts, 4);
|
||||
|
||||
m = get_cubic_roots (pts[0].y, pts[1].y, pts[2].y, pts[3].y, t);
|
||||
|
||||
j = 0;
|
||||
for (i = 0; i < m; i++)
|
||||
{
|
||||
t2[j] = t[i];
|
||||
gsk_curve_get_point (curve2, t2[j], &p[j]);
|
||||
find_point_on_line (a, b, &p[j], &t1[j]);
|
||||
if (acceptable (t1[j]))
|
||||
j++;
|
||||
if (j == n)
|
||||
break;
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
#define MAX_LEVEL 25
|
||||
#define TOLERANCE 0.001
|
||||
|
||||
static void
|
||||
cubic_intersect_recurse (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float t1l,
|
||||
float t1r,
|
||||
float t2l,
|
||||
float t2r,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n,
|
||||
int *pos,
|
||||
int level)
|
||||
{
|
||||
GskCurve p11, p12, p21, p22;
|
||||
GskBoundingBox b1, b2;
|
||||
float d1, d2;
|
||||
|
||||
if (*pos == n)
|
||||
return;
|
||||
|
||||
if (level == MAX_LEVEL)
|
||||
return;
|
||||
|
||||
gsk_curve_get_bounds (curve1, &b1);
|
||||
gsk_curve_get_bounds (curve2, &b2);
|
||||
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
return;
|
||||
|
||||
gsk_curve_get_tight_bounds (curve1, &b1);
|
||||
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
return;
|
||||
|
||||
gsk_curve_get_tight_bounds (curve2, &b2);
|
||||
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
return;
|
||||
|
||||
d1 = (t1r - t1l) / 2;
|
||||
d2 = (t2r - t2l) / 2;
|
||||
|
||||
if (b1.max.x - b1.min.x < TOLERANCE && b1.max.y - b1.min.y < TOLERANCE &&
|
||||
b2.max.x - b2.min.x < TOLERANCE && b2.max.y - b2.min.y < TOLERANCE)
|
||||
{
|
||||
graphene_point_t c;
|
||||
t1[*pos] = t1l + d1;
|
||||
t2[*pos] = t2l + d2;
|
||||
gsk_curve_get_point (curve1, 0.5, &c);
|
||||
|
||||
for (int i = 0; i < *pos; i++)
|
||||
{
|
||||
if (graphene_point_near (&c, &p[i], 0.1))
|
||||
return;
|
||||
}
|
||||
|
||||
p[*pos] = c;
|
||||
(*pos)++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
gsk_curve_split (curve1, 0.5, &p11, &p12);
|
||||
gsk_curve_split (curve2, 0.5, &p21, &p22);
|
||||
|
||||
cubic_intersect_recurse (&p11, &p21, t1l, t1l + d1, t2l, t2l + d2, t1, t2, p, n, pos, level + 1);
|
||||
cubic_intersect_recurse (&p11, &p22, t1l, t1l + d1, t2l + d2, t2r, t1, t2, p, n, pos, level + 1);
|
||||
cubic_intersect_recurse (&p12, &p21, t1l + d1, t1r, t2l, t2l + d2, t1, t2, p, n, pos, level + 1);
|
||||
cubic_intersect_recurse (&p12, &p22, t1l + d1, t1r, t2l + d2, t2r, t1, t2, p, n, pos, level + 1);
|
||||
}
|
||||
|
||||
static int
|
||||
cubic_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
cubic_intersect_recurse (curve1, curve2, 0, 1, 0, 1, t1, t2, p, n, &pos, 0);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static void
|
||||
get_bounds (const GskCurve *curve,
|
||||
float tl,
|
||||
float tr,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_segment (curve, tl, tr, &c);
|
||||
gsk_curve_get_tight_bounds (&c, bounds);
|
||||
}
|
||||
|
||||
static void
|
||||
general_intersect_recurse (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float t1l,
|
||||
float t1r,
|
||||
float t2l,
|
||||
float t2r,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n,
|
||||
int *pos,
|
||||
int level)
|
||||
{
|
||||
GskBoundingBox b1, b2;
|
||||
float d1, d2;
|
||||
|
||||
if (*pos == n)
|
||||
return;
|
||||
|
||||
if (level == MAX_LEVEL)
|
||||
return;
|
||||
|
||||
get_bounds (curve1, t1l, t1r, &b1);
|
||||
get_bounds (curve2, t2l, t2r, &b2);
|
||||
|
||||
if (!gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
return;
|
||||
|
||||
d1 = (t1r - t1l) / 2;
|
||||
d2 = (t2r - t2l) / 2;
|
||||
|
||||
if (b1.max.x - b1.min.x < TOLERANCE && b1.max.y - b1.min.y < TOLERANCE &&
|
||||
b2.max.x - b2.min.x < TOLERANCE && b2.max.y - b2.min.y < TOLERANCE)
|
||||
{
|
||||
graphene_point_t c;
|
||||
t1[*pos] = t1l + d1;
|
||||
t2[*pos] = t2l + d2;
|
||||
gsk_curve_get_point (curve1, t1[*pos], &c);
|
||||
|
||||
for (int i = 0; i < *pos; i++)
|
||||
{
|
||||
if (graphene_point_near (&c, &p[i], 0.1))
|
||||
return;
|
||||
}
|
||||
|
||||
p[*pos] = c;
|
||||
(*pos)++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note that in the conic case, we cannot just split the curves and
|
||||
* pass the two halves down, since splitting changes the parametrization,
|
||||
* and we need the t's to be valid parameters wrt to the original curve.
|
||||
*
|
||||
* So, instead, we determine the bounding boxes above by always starting
|
||||
* from the original curve. That is a bit less efficient, but also works
|
||||
* for conics.
|
||||
*/
|
||||
general_intersect_recurse (curve1, curve2, t1l, t1l + d1, t2l, t2l + d2, t1, t2, p, n, pos, level + 1);
|
||||
general_intersect_recurse (curve1, curve2, t1l, t1l + d1, t2l + d2, t2r, t1, t2, p, n, pos, level + 1);
|
||||
general_intersect_recurse (curve1, curve2, t1l + d1, t1r, t2l, t2l + d2, t1, t2, p, n, pos, level + 1);
|
||||
general_intersect_recurse (curve1, curve2, t1l + d1, t1r, t2l + d2, t2r, t1, t2, p, n, pos, level + 1);
|
||||
}
|
||||
|
||||
static int
|
||||
general_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
general_intersect_recurse (curve1, curve2, 0, 1, 0, 1, t1, t2, p, n, &pos, 0);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static int
|
||||
curve_self_intersect (const GskCurve *curve,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
float tt[3], ss[3], s;
|
||||
graphene_point_t pp[3];
|
||||
int m;
|
||||
GskCurve cs, ce;
|
||||
|
||||
if (curve->op != GSK_PATH_CUBIC)
|
||||
return 0;
|
||||
|
||||
s = 0.5;
|
||||
m = gsk_curve_get_curvature_points (curve, tt);
|
||||
for (int i = 0; i < m; i++)
|
||||
{
|
||||
if (gsk_curve_get_curvature (curve, tt[i], NULL) == 0)
|
||||
{
|
||||
s = tt[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gsk_curve_split (curve, s, &cs, &ce);
|
||||
|
||||
m = cubic_intersect (&cs, &ce, tt, ss, pp, 3);
|
||||
|
||||
if (m > 1)
|
||||
{
|
||||
/* One of the (at most 2) intersections we found
|
||||
* must be the common point where we split the curve.
|
||||
* It will have a t value of 1 and an s value of 0.
|
||||
*/
|
||||
if (fabs (tt[0] - 1) > 1e-3)
|
||||
{
|
||||
t1[0] = t2[0] = tt[0] * s;
|
||||
p[0] = pp[0];
|
||||
}
|
||||
else if (fabs (tt[1] - 1) > 1e-3)
|
||||
{
|
||||
t1[0] = t2[0] = tt[1] * s;
|
||||
p[0] = pp[1];
|
||||
}
|
||||
|
||||
if (n == 1)
|
||||
return 1;
|
||||
|
||||
if (fabs (ss[0]) > 1e-3)
|
||||
{
|
||||
t1[1] = t2[1] = s + ss[0] * (1 - s);
|
||||
p[1] = pp[0];
|
||||
}
|
||||
else if (fabs (ss[1]) > 1e-3)
|
||||
{
|
||||
t1[1] = t2[1] = s + ss[1] * (1 - s);
|
||||
p[1] = pp[1];
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
curve_equal (const GskCurve *c1,
|
||||
const GskCurve *c2)
|
||||
{
|
||||
gsize curve_size[] = {
|
||||
sizeof (GskLineCurve),
|
||||
sizeof (GskLineCurve),
|
||||
sizeof (GskLineCurve),
|
||||
sizeof (GskQuadCurve),
|
||||
sizeof (GskCubicCurve),
|
||||
sizeof (GskConicCurve)
|
||||
};
|
||||
|
||||
return c1->op == c2->op && memcmp (c1, c2, curve_size[c1->op]) == 0;
|
||||
}
|
||||
|
||||
/* Place intersections between the curves in p, and their Bezier positions
|
||||
* in t1 and t2, up to n. Return the number of intersections found.
|
||||
*
|
||||
* Note that two cubic Beziers can have up to 9 intersections.
|
||||
*/
|
||||
int
|
||||
gsk_curve_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
int n)
|
||||
{
|
||||
GskPathOperation op1 = curve1->op;
|
||||
GskPathOperation op2 = curve2->op;
|
||||
|
||||
if (op1 == GSK_PATH_CLOSE)
|
||||
op1 = GSK_PATH_LINE;
|
||||
|
||||
if (op2 == GSK_PATH_CLOSE)
|
||||
op2 = GSK_PATH_LINE;
|
||||
|
||||
if (curve_equal (curve1, curve2))
|
||||
return curve_self_intersect (curve1, t1, t2, p, n);
|
||||
|
||||
/* We special-case line-line and line-cubic intersections,
|
||||
* since we can solve them directly.
|
||||
* Everything else is done via bisection.
|
||||
*/
|
||||
if (op1 == GSK_PATH_LINE && op2 == GSK_PATH_LINE)
|
||||
return line_intersect (curve1, curve2, t1, t2, p, n);
|
||||
else if (op1 == GSK_PATH_LINE && op2 == GSK_PATH_QUAD)
|
||||
return line_quad_intersect (curve1, curve2, t1, t2, p, n);
|
||||
else if (op1 == GSK_PATH_QUAD && op2 == GSK_PATH_LINE)
|
||||
return line_quad_intersect (curve2, curve1, t2, t1, p, n);
|
||||
else if (op1 == GSK_PATH_LINE && op2 == GSK_PATH_CUBIC)
|
||||
return line_cubic_intersect (curve1, curve2, t1, t2, p, n);
|
||||
else if (op1 == GSK_PATH_CUBIC && op2 == GSK_PATH_LINE)
|
||||
return line_cubic_intersect (curve2, curve1, t2, t1, p, n);
|
||||
else if ((op1 == GSK_PATH_QUAD || op1 == GSK_PATH_CUBIC) &&
|
||||
(op2 == GSK_PATH_QUAD || op2 == GSK_PATH_CUBIC))
|
||||
return cubic_intersect (curve1, curve2, t1, t2, p, n);
|
||||
else
|
||||
return general_intersect (curve1, curve2, t1, t2, p, n);
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* vim:set foldmethod=marker expandtab: */
|
181
gsk/gskcurveprivate.h
Normal file
181
gsk/gskcurveprivate.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpathopprivate.h"
|
||||
#include "gskpath.h"
|
||||
#include "gskboundingboxprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
typedef union _GskCurve GskCurve;
|
||||
|
||||
typedef struct _GskLineCurve GskLineCurve;
|
||||
typedef struct _GskQuadCurve GskQuadCurve;
|
||||
typedef struct _GskCubicCurve GskCubicCurve;
|
||||
typedef struct _GskConicCurve GskConicCurve;
|
||||
|
||||
struct _GskLineCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean padding;
|
||||
|
||||
graphene_point_t points[2];
|
||||
};
|
||||
|
||||
struct _GskQuadCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[3];
|
||||
|
||||
graphene_point_t coeffs[3];
|
||||
};
|
||||
|
||||
struct _GskCubicCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[4];
|
||||
|
||||
graphene_point_t coeffs[4];
|
||||
};
|
||||
|
||||
struct _GskConicCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
/* points[0], points[1], points[3] are the control points,
|
||||
* points[2].x is the weight
|
||||
*/
|
||||
graphene_point_t points[4];
|
||||
|
||||
graphene_point_t num[3];
|
||||
graphene_point_t denom[3];
|
||||
};
|
||||
|
||||
union _GskCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
GskLineCurve line;
|
||||
GskQuadCurve quad;
|
||||
GskCubicCurve cubic;
|
||||
GskConicCurve conic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GSK_CURVE_LINE_REASON_STRAIGHT,
|
||||
GSK_CURVE_LINE_REASON_SHORT
|
||||
} GskCurveLineReason;
|
||||
|
||||
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data);
|
||||
|
||||
typedef gboolean (* GskCurveAddCurveFunc) (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data);
|
||||
|
||||
void gsk_curve_init (GskCurve *curve,
|
||||
gskpathop op);
|
||||
void gsk_curve_init_foreach (GskCurve *curve,
|
||||
GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight);
|
||||
|
||||
void gsk_curve_print (const GskCurve *curve,
|
||||
GString *string);
|
||||
|
||||
char * gsk_curve_to_string (const GskCurve *curve);
|
||||
|
||||
gskpathop gsk_curve_pathop (const GskCurve *curve);
|
||||
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
|
||||
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
|
||||
void gsk_curve_get_start_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_get_end_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_get_point (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_point_t *pos);
|
||||
void gsk_curve_get_tangent (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_reverse (const GskCurve *curve,
|
||||
GskCurve *reverse);
|
||||
void gsk_curve_split (const GskCurve *curve,
|
||||
float progress,
|
||||
GskCurve *start,
|
||||
GskCurve *end);
|
||||
void gsk_curve_segment (const GskCurve *curve,
|
||||
float start,
|
||||
float end,
|
||||
GskCurve *segment);
|
||||
gboolean gsk_curve_decompose (const GskCurve *curve,
|
||||
float tolerance,
|
||||
GskCurveAddLineFunc add_line_func,
|
||||
gpointer user_data);
|
||||
gboolean gsk_curve_decompose_curve (const GskCurve *curve,
|
||||
GskPathForeachFlags flags,
|
||||
float tolerance,
|
||||
GskCurveAddCurveFunc add_curve_func,
|
||||
gpointer user_data);
|
||||
|
||||
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
|
||||
|
||||
float gsk_curve_get_curvature (const GskCurve *curve,
|
||||
float t,
|
||||
graphene_point_t *center);
|
||||
void gsk_curve_get_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds);
|
||||
void gsk_curve_get_tight_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds);
|
||||
|
||||
int gsk_curve_get_curvature_points (const GskCurve *curve,
|
||||
float t[3]);
|
||||
|
||||
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,
|
||||
int n);
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -170,6 +170,49 @@ typedef enum {
|
||||
GSK_CORNER_BOTTOM_LEFT
|
||||
} GskCorner;
|
||||
|
||||
/**
|
||||
* GskPathOperation:
|
||||
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the target point.
|
||||
* @GSK_PATH_CLOSE: A close operation ending the current contour with a line back
|
||||
* to the starting point. Two points describe the start and end of the line.
|
||||
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the start and
|
||||
* end point of a straight line.
|
||||
* @GSK_PATH_QUAD: A curve-to operation describing a quadratic Bézier curve
|
||||
* with 3 points describing the start point, the control point and the end
|
||||
* point of the curve.
|
||||
* @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve with 4
|
||||
* points describing the start point, the two control points and the end point
|
||||
* of the curve.
|
||||
* @GSK_PATH_CONIC: A weighted quadratic Bézier curve with 3 points describing
|
||||
* the start point, control point and end point of the curve. A weight for the
|
||||
* curve will be passed, too.
|
||||
*
|
||||
* Path operations can be used to approximate a `GskPath`.
|
||||
*
|
||||
* More values may be added in the future.
|
||||
**/
|
||||
typedef enum {
|
||||
GSK_PATH_MOVE,
|
||||
GSK_PATH_CLOSE,
|
||||
GSK_PATH_LINE,
|
||||
GSK_PATH_QUAD,
|
||||
GSK_PATH_CUBIC,
|
||||
GSK_PATH_CONIC,
|
||||
} GskPathOperation;
|
||||
|
||||
/**
|
||||
* GskPathDirection:
|
||||
* @GSK_PATH_START: The side that leads to the start of the path
|
||||
* @GSK_PATH_END: The side that leads to the end of the path
|
||||
*
|
||||
* The values of the `GskPathDirection` enum are used to pick one
|
||||
* of the two sides of the path that at a given point on the path.
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_PATH_START,
|
||||
GSK_PATH_END
|
||||
} GskPathDirection;
|
||||
|
||||
/**
|
||||
* GskSerializationError:
|
||||
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified
|
||||
@@ -274,4 +317,3 @@ typedef enum
|
||||
GSK_MASK_MODE_LUMINANCE,
|
||||
GSK_MASK_MODE_INVERTED_LUMINANCE
|
||||
} GskMaskMode;
|
||||
|
||||
|
1187
gsk/gskpath.c
Normal file
1187
gsk/gskpath.c
Normal file
File diff suppressed because it is too large
Load Diff
98
gsk/gskpath.h
Normal file
98
gsk/gskpath.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
GSK_FILL_RULE_WINDING,
|
||||
GSK_FILL_RULE_EVEN_ODD
|
||||
} GskFillRule;
|
||||
|
||||
/**
|
||||
* GskPathForeachFlags:
|
||||
* @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of `GSK_PATH_QUAD` operations
|
||||
* @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations.
|
||||
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of `GSK_PATH_CONIC` operations.
|
||||
*
|
||||
* Flags that can be passed to gsk_path_foreach() to enable additional
|
||||
* features.
|
||||
*
|
||||
* By default, [method@Gsk.Path.foreach] will only emit a path with all operations
|
||||
* flattened to straight lines to allow for maximum compatibility. The only
|
||||
* operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
|
||||
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2),
|
||||
} GskPathForeachFlags;
|
||||
|
||||
/**
|
||||
* GskPathForeachFunc:
|
||||
* @op: The operation to perform
|
||||
* @pts: The points of the operation
|
||||
* @n_pts: The number of points
|
||||
* @weight: The weight for conic curves, or unused if not a conic curve.
|
||||
* @user_data: The user data provided with the function
|
||||
*
|
||||
* Prototype of the callback to iterate throught the operations of
|
||||
* a path.
|
||||
*
|
||||
* Returns: %TRUE to continue evaluating the path, %FALSE to
|
||||
* immediately abort and not call the function again.
|
||||
*/
|
||||
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data);
|
||||
|
||||
#define GSK_TYPE_PATH (gsk_path_get_type ())
|
||||
|
||||
GType gsk_path_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GskPath * gsk_path_ref (GskPath *self);
|
||||
void gsk_path_unref (GskPath *self);
|
||||
void gsk_path_print (GskPath *self,
|
||||
GString *string);
|
||||
char * gsk_path_to_string (GskPath *self);
|
||||
GskPath * gsk_path_parse (const char *string);
|
||||
void gsk_path_to_cairo (GskPath *self,
|
||||
cairo_t *cr);
|
||||
gboolean gsk_path_is_empty (GskPath *self);
|
||||
gboolean gsk_path_get_bounds (GskPath *self,
|
||||
graphene_rect_t *bounds);
|
||||
gboolean gsk_path_foreach (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
|
||||
|
||||
G_END_DECLS
|
1062
gsk/gskpathbuilder.c
Normal file
1062
gsk/gskpathbuilder.c
Normal file
File diff suppressed because it is too large
Load Diff
118
gsk/gskpathbuilder.h
Normal file
118
gsk/gskpathbuilder.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gskroundedrect.h>
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
|
||||
|
||||
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GskPathBuilder * gsk_path_builder_new (void);
|
||||
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *self);
|
||||
void gsk_path_builder_unref (GskPathBuilder *self);
|
||||
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
GskPath * gsk_path_builder_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *self);
|
||||
|
||||
void gsk_path_builder_add_path (GskPathBuilder *self,
|
||||
GskPath *path);
|
||||
void gsk_path_builder_add_reverse_path (GskPathBuilder *self,
|
||||
GskPath *path);
|
||||
void gsk_path_builder_add_cairo_path (GskPathBuilder *self,
|
||||
const cairo_path_t *path);
|
||||
void gsk_path_builder_add_layout (GskPathBuilder *self,
|
||||
PangoLayout *layout);
|
||||
void gsk_path_builder_add_rect (GskPathBuilder *self,
|
||||
const graphene_rect_t *rect);
|
||||
void gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
|
||||
const GskRoundedRect *rect);
|
||||
void gsk_path_builder_add_circle (GskPathBuilder *self,
|
||||
const graphene_point_t *center,
|
||||
float radius);
|
||||
void gsk_path_builder_add_ellipse (GskPathBuilder *self,
|
||||
const graphene_point_t *center,
|
||||
const graphene_size_t *radius);
|
||||
void gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||
GskPathMeasure *measure,
|
||||
float start,
|
||||
float end);
|
||||
void gsk_path_builder_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
void gsk_path_builder_rel_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
void gsk_path_builder_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
void gsk_path_builder_rel_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
void gsk_path_builder_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
void gsk_path_builder_rel_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
void gsk_path_builder_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
void gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
void gsk_path_builder_conic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float weight);
|
||||
void gsk_path_builder_rel_conic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float weight);
|
||||
void gsk_path_builder_close (GskPathBuilder *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
|
||||
|
||||
G_END_DECLS
|
489
gsk/gskpathmeasure.c
Normal file
489
gsk/gskpathmeasure.c
Normal file
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gskpathmeasure.h"
|
||||
|
||||
#include "gskpathbuilder.h"
|
||||
#include "gskpathpointprivate.h"
|
||||
#include "gskpathprivate.h"
|
||||
|
||||
/*< private >
|
||||
* `GskPathMeasure` is an object that allows measurements
|
||||
* on `GskPath`s such as determining the length of the path.
|
||||
*
|
||||
* Many measuring operations require approximating the path
|
||||
* with simpler shapes. Therefore, a `GskPathMeasure` has
|
||||
* a tolerance that determines what amount is required
|
||||
* for such approximations.
|
||||
*
|
||||
* A `GskPathMeasure` struct is a reference counted struct
|
||||
* and should be treated as opaque.
|
||||
*/
|
||||
|
||||
typedef struct _GskContourMeasure GskContourMeasure;
|
||||
|
||||
struct _GskContourMeasure
|
||||
{
|
||||
float length;
|
||||
gpointer contour_data;
|
||||
};
|
||||
|
||||
struct _GskPathMeasure
|
||||
{
|
||||
/*< private >*/
|
||||
guint ref_count;
|
||||
|
||||
GskPath *path;
|
||||
float tolerance;
|
||||
|
||||
float length;
|
||||
gsize n_contours;
|
||||
GskContourMeasure measures[];
|
||||
};
|
||||
|
||||
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
|
||||
gsk_path_measure_ref,
|
||||
gsk_path_measure_unref)
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_new:
|
||||
* @path: the path to measure
|
||||
*
|
||||
* Creates a measure object for the given @path.
|
||||
*
|
||||
* Returns: a new `GskPathMeasure` representing @path
|
||||
*/
|
||||
GskPathMeasure *
|
||||
gsk_path_measure_new (GskPath *path)
|
||||
{
|
||||
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_new_with_tolerance:
|
||||
* @path: the path to measure
|
||||
* @tolerance: the tolerance for measuring operations
|
||||
*
|
||||
* Creates a measure object for the given @path and @tolerance.
|
||||
*
|
||||
* Returns: a new `GskPathMeasure` representing @path
|
||||
*/
|
||||
GskPathMeasure *
|
||||
gsk_path_measure_new_with_tolerance (GskPath *path,
|
||||
float tolerance)
|
||||
{
|
||||
GskPathMeasure *self;
|
||||
gsize i, n_contours;
|
||||
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
g_return_val_if_fail (tolerance > 0, NULL);
|
||||
|
||||
n_contours = gsk_path_get_n_contours (path);
|
||||
|
||||
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
|
||||
|
||||
self->ref_count = 1;
|
||||
self->path = gsk_path_ref (path);
|
||||
self->tolerance = tolerance;
|
||||
self->n_contours = n_contours;
|
||||
|
||||
for (i = 0; i < n_contours; i++)
|
||||
{
|
||||
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
|
||||
self->tolerance,
|
||||
&self->measures[i].length);
|
||||
self->length += self->measures[i].length;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_ref:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Increases the reference count of a `GskPathMeasure` by one.
|
||||
*
|
||||
* Returns: the passed in `GskPathMeasure`.
|
||||
*/
|
||||
GskPathMeasure *
|
||||
gsk_path_measure_ref (GskPathMeasure *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
self->ref_count++;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_unref:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Decreases the reference count of a `GskPathMeasure` by one.
|
||||
*
|
||||
* If the resulting reference count is zero, frees the object.
|
||||
*/
|
||||
void
|
||||
gsk_path_measure_unref (GskPathMeasure *self)
|
||||
{
|
||||
gsize i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
|
||||
self->ref_count--;
|
||||
if (self->ref_count > 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
{
|
||||
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data);
|
||||
}
|
||||
|
||||
gsk_path_unref (self->path);
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_get_path:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Returns the path that the measure was created for.
|
||||
*
|
||||
* Returns: (transfer none): the path of @self
|
||||
*/
|
||||
GskPath *
|
||||
gsk_path_measure_get_path (GskPathMeasure *self)
|
||||
{
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_get_tolerance:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Returns the tolerance that the measure was created with.
|
||||
*
|
||||
* Returns: the tolerance of @self
|
||||
*/
|
||||
float
|
||||
gsk_path_measure_get_tolerance (GskPathMeasure *self)
|
||||
{
|
||||
return self->tolerance;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_get_length:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Gets the length of the path being measured.
|
||||
*
|
||||
* The length is cached, so this function does not do any work.
|
||||
*
|
||||
* Returns: The length of the path measured by @self
|
||||
*/
|
||||
float
|
||||
gsk_path_measure_get_length (GskPathMeasure *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0);
|
||||
|
||||
return self->length;
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_is_closed:
|
||||
* @self: a `GskPathMeasure`
|
||||
*
|
||||
* Returns if the path being measured represents a single closed
|
||||
* contour.
|
||||
*
|
||||
* Returns: `TRUE` if the current path is closed
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_measure_is_closed (GskPathMeasure *self)
|
||||
{
|
||||
const GskContour *contour;
|
||||
|
||||
g_return_val_if_fail (self != NULL, FALSE);
|
||||
|
||||
/* XXX: is the empty path closed? Currently it's not */
|
||||
if (self->n_contours != 1)
|
||||
return FALSE;
|
||||
|
||||
contour = gsk_path_get_contour (self->path, 0);
|
||||
return gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_path_measure_clamp_distance (GskPathMeasure *self,
|
||||
float distance)
|
||||
{
|
||||
if (isnan (distance))
|
||||
return 0;
|
||||
|
||||
return CLAMP (distance, 0, self->length);
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_in_fill:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @point: the point to test
|
||||
* @fill_rule: the fill rule to follow
|
||||
*
|
||||
* Returns whether the given point is inside the area
|
||||
* that would be affected if the path was filled according
|
||||
* to @fill_rule.
|
||||
*
|
||||
* Returns: `TRUE` if @point is inside
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_measure_in_fill (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
int winding = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data,
|
||||
point);
|
||||
|
||||
switch (fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
return winding & 1;
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
return winding != 0;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
|
||||
GskPathMeasure *measure,
|
||||
gboolean emit_move_to,
|
||||
float start,
|
||||
float end)
|
||||
{
|
||||
g_assert (start < end);
|
||||
|
||||
for (gsize i = 0; i < measure->n_contours; i++)
|
||||
{
|
||||
if (measure->measures[i].length < start)
|
||||
{
|
||||
start -= measure->measures[i].length;
|
||||
end -= measure->measures[i].length;
|
||||
}
|
||||
else if (start > 0 || end < measure->measures[i].length)
|
||||
{
|
||||
float len = MIN (end, measure->measures[i].length);
|
||||
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
|
||||
self,
|
||||
measure->measures[i].contour_data,
|
||||
emit_move_to,
|
||||
start,
|
||||
len);
|
||||
end -= len;
|
||||
start = 0;
|
||||
if (end <= 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
end -= measure->measures[i].length;
|
||||
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
|
||||
}
|
||||
emit_move_to = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_builder_add_segment:
|
||||
* @self: a `GskPathBuilder`
|
||||
* @measure: the `GskPathMeasure` to take the segment to
|
||||
* @start: start distance into the path
|
||||
* @end: end distance into the path
|
||||
*
|
||||
* Adds to @self the segment of @measure from @start to @end.
|
||||
*
|
||||
* The distances are given relative to the length of @measure's path,
|
||||
* from 0 for the beginning of the path to its length for the end
|
||||
* of the path. The values will be clamped to that range. The length
|
||||
* can be obtained with [method@Gsk.PathMeasure.get_length].
|
||||
*
|
||||
* If @start >= @end after clamping, the path will first add the segment
|
||||
* from @start to the end of the path, and then add the segment from
|
||||
* the beginning to @end. If the path is closed, these segments will
|
||||
* be connected.
|
||||
*/
|
||||
void
|
||||
gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||
GskPathMeasure *measure,
|
||||
float start,
|
||||
float end)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (measure != NULL);
|
||||
|
||||
start = gsk_path_measure_clamp_distance (measure, start);
|
||||
end = gsk_path_measure_clamp_distance (measure, end);
|
||||
|
||||
if (start < end)
|
||||
{
|
||||
gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the path is closed, we can connect the 2 subpaths. */
|
||||
gboolean closed = gsk_path_measure_is_closed (measure);
|
||||
gboolean need_move_to = !closed;
|
||||
|
||||
if (start < measure->length)
|
||||
gsk_path_builder_add_segment_chunk (self, measure,
|
||||
TRUE,
|
||||
start, measure->length);
|
||||
else
|
||||
need_move_to = TRUE;
|
||||
|
||||
if (end > 0)
|
||||
gsk_path_builder_add_segment_chunk (self, measure,
|
||||
need_move_to,
|
||||
0, end);
|
||||
if (start == end && closed)
|
||||
gsk_path_builder_close (self);
|
||||
}
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_get_point:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @distance: the distance
|
||||
*
|
||||
* Returns a `GskPathPoint` representing the point at the given
|
||||
* distance into the path.
|
||||
*
|
||||
* An empty path has no points, so `NULL` is returned in that case.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a newly allocated `GskPathPoint`
|
||||
*/
|
||||
GskPathPoint *
|
||||
gsk_path_measure_get_point (GskPathMeasure *self,
|
||||
float distance)
|
||||
{
|
||||
gsize i;
|
||||
float contour_offset;
|
||||
float offset;
|
||||
const GskContour *contour;
|
||||
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
if (self->n_contours == 0)
|
||||
return NULL;
|
||||
|
||||
contour_offset = 0;
|
||||
offset = gsk_path_measure_clamp_distance (self, distance);
|
||||
|
||||
for (i = 0; i < self->n_contours - 1; i++)
|
||||
{
|
||||
if (offset < self->measures[i].length)
|
||||
break;
|
||||
|
||||
contour_offset += self->measures[i].length;
|
||||
offset -= self->measures[i].length;
|
||||
}
|
||||
|
||||
g_assert (0 <= i && i < self->n_contours);
|
||||
|
||||
offset = CLAMP (offset, 0, self->measures[i].length);
|
||||
|
||||
contour = gsk_path_get_contour (self->path, i);
|
||||
|
||||
return gsk_path_point_new (self,
|
||||
contour, self->measures[i].contour_data,
|
||||
contour_offset, offset);
|
||||
}
|
||||
|
||||
/*< private >
|
||||
* gsk_path_measure_get_closest_point:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @point: the point to fond the closest point to
|
||||
* @threshold: The maximum allowed distance between the path and @point.
|
||||
* Use `INFINITY` to look for any point.
|
||||
*
|
||||
* Returns a `GskPathPoint` representing the point on the path
|
||||
* that is closest to the given point.
|
||||
*
|
||||
* If no point on the path is closer than @threshold, `NULL` is returned.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a newly allocated `GskPathPoint`
|
||||
*/
|
||||
GskPathPoint *
|
||||
gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold)
|
||||
{
|
||||
gssize best_idx;
|
||||
float best_offset;
|
||||
float best_contour_offset;
|
||||
float contour_offset;
|
||||
|
||||
contour_offset = 0;
|
||||
best_idx = -1;
|
||||
|
||||
for (gsize i = 0; i < self->n_contours; i++)
|
||||
{
|
||||
float distance, offset;
|
||||
|
||||
if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data,
|
||||
self->tolerance,
|
||||
point,
|
||||
threshold,
|
||||
&distance,
|
||||
NULL,
|
||||
&offset,
|
||||
NULL))
|
||||
{
|
||||
best_idx = i;
|
||||
best_offset = offset;
|
||||
best_contour_offset = contour_offset;
|
||||
|
||||
if (distance < self->tolerance)
|
||||
break;
|
||||
|
||||
threshold = distance - self->tolerance;
|
||||
}
|
||||
|
||||
contour_offset += self->measures[i].length;
|
||||
}
|
||||
|
||||
if (best_idx != -1)
|
||||
return gsk_path_point_new (self,
|
||||
gsk_path_get_contour (self->path, best_idx),
|
||||
self->measures[best_idx].contour_data,
|
||||
best_contour_offset, best_offset);
|
||||
|
||||
return NULL;
|
||||
}
|
55
gsk/gskpathmeasure.h
Normal file
55
gsk/gskpathmeasure.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gskpath.h>
|
||||
#include <gsk/gskpathpoint.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
|
||||
|
||||
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
|
||||
GskPathMeasure * gsk_path_measure_new (GskPath *path);
|
||||
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
|
||||
float tolerance);
|
||||
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
|
||||
void gsk_path_measure_unref (GskPathMeasure *self);
|
||||
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
|
||||
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
|
||||
float gsk_path_measure_get_length (GskPathMeasure *self);
|
||||
gboolean gsk_path_measure_is_closed (GskPathMeasure *self);
|
||||
gboolean gsk_path_measure_in_fill (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
GskFillRule fill_rule);
|
||||
GskPathPoint * gsk_path_measure_get_point (GskPathMeasure *self,
|
||||
float distance);
|
||||
GskPathPoint * gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
|
||||
|
||||
G_END_DECLS
|
186
gsk/gskpathopprivate.h
Normal file
186
gsk/gskpathopprivate.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpath.h"
|
||||
#include "gskpathbuilder.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
static inline
|
||||
gskpathop gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts);
|
||||
static inline
|
||||
const graphene_point_t *gsk_pathop_points (gskpathop pop);
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop);
|
||||
|
||||
static inline
|
||||
gboolean gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
/* included inline so tests can use them */
|
||||
static inline
|
||||
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
static inline
|
||||
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
|
||||
/* IMPLEMENTATION */
|
||||
|
||||
#define GSK_PATHOP_OPERATION_MASK (0x7)
|
||||
|
||||
static inline gskpathop
|
||||
gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts)
|
||||
{
|
||||
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
|
||||
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
|
||||
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
|
||||
}
|
||||
|
||||
static inline const graphene_point_t *
|
||||
gsk_pathop_points (gskpathop pop)
|
||||
{
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
|
||||
}
|
||||
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop)
|
||||
{
|
||||
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
switch (gsk_pathop_op (pop))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data);
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (pop);
|
||||
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
|
||||
}
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (op))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (op))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_line_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
G_END_DECLS
|
1726
gsk/gskpathops.c
Normal file
1726
gsk/gskpathops.c
Normal file
File diff suppressed because it is too large
Load Diff
195
gsk/gskpathpoint.c
Normal file
195
gsk/gskpathpoint.c
Normal file
@@ -0,0 +1,195 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "gskpathpointprivate.h"
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
#include "gskpathmeasure.h"
|
||||
|
||||
#include "gdk/gdkprivate.h"
|
||||
|
||||
|
||||
/**
|
||||
* GskPathPoint:
|
||||
*
|
||||
* `GskPathPoint` is an opaque, immutable type representing a point on a path.
|
||||
*
|
||||
* It can be queried for properties of the path at that point, such as its
|
||||
* tangent or its curvature.
|
||||
*
|
||||
* To obtain a `GskPathPoint`, use [method@Gsk.PathMeasure.get_path_point]
|
||||
* or [method@Gsk.PathMeasure.get_closest_point].
|
||||
*/
|
||||
|
||||
struct _GskPathPoint
|
||||
{
|
||||
guint ref_count;
|
||||
|
||||
GskPathMeasure *measure;
|
||||
const GskContour *contour;
|
||||
gpointer measure_data;
|
||||
float contour_offset; /* distance from beginning of path to contour */
|
||||
float offset; /* offset of point inside contour */
|
||||
};
|
||||
|
||||
G_DEFINE_BOXED_TYPE (GskPathPoint, gsk_path_point,
|
||||
gsk_path_point_ref,
|
||||
gsk_path_point_unref)
|
||||
|
||||
GskPathPoint *
|
||||
gsk_path_point_new (GskPathMeasure *measure,
|
||||
const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float contour_offset,
|
||||
float offset)
|
||||
{
|
||||
GskPathPoint *self;
|
||||
|
||||
self = g_new0 (GskPathPoint, 1);
|
||||
|
||||
self->ref_count = 1;
|
||||
|
||||
self->measure = gsk_path_measure_ref (measure);
|
||||
self->contour = contour;
|
||||
self->measure_data = measure_data;
|
||||
self->contour_offset = contour_offset;
|
||||
self->offset = offset;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_ref:
|
||||
* @self: a `GskPathPoint`
|
||||
*
|
||||
* Increases the reference count of a `GskPathPoint` by one.
|
||||
*
|
||||
* Returns: the passed in `GskPathPoint`
|
||||
*/
|
||||
GskPathPoint *
|
||||
gsk_path_point_ref (GskPathPoint *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
self->ref_count++;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_unref:
|
||||
* @self: a `GskPathPoint`
|
||||
*
|
||||
* Decreases the reference count of a `GskPathPoint` by one.
|
||||
*
|
||||
* If the resulting reference count is zero, frees the path_measure.
|
||||
*/
|
||||
void
|
||||
gsk_path_point_unref (GskPathPoint *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
|
||||
self->ref_count--;
|
||||
if (self->ref_count > 0)
|
||||
return;
|
||||
|
||||
gsk_path_measure_unref (self->measure);
|
||||
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
GskPathMeasure *
|
||||
gsk_path_point_get_measure (GskPathPoint *self)
|
||||
{
|
||||
return self->measure;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_distance:
|
||||
* @self: a `GskPathPoint`
|
||||
*
|
||||
* Returns the distance of the given point from the start of the path.
|
||||
*
|
||||
* This is the length of the contour from the beginning of the path
|
||||
* to the point.
|
||||
*
|
||||
* Returns: The offset of point in path
|
||||
*/
|
||||
float
|
||||
gsk_path_point_get_distance (GskPathPoint *self)
|
||||
{
|
||||
return self->contour_offset + self->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_position:
|
||||
* @self: a `GskPathPoint`
|
||||
* @position: (out caller-allocates): Return location for
|
||||
* the coordinates of the point
|
||||
*
|
||||
* Gets the position of the point.
|
||||
*/
|
||||
void
|
||||
gsk_path_point_get_position (GskPathPoint *self,
|
||||
graphene_point_t *position)
|
||||
{
|
||||
gsk_contour_get_point (self->contour,
|
||||
self->measure_data,
|
||||
self->offset,
|
||||
GSK_PATH_END,
|
||||
position, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_tangent:
|
||||
* @self: a `GskPathPoint`
|
||||
* @direction: the direction for which to return the tangent
|
||||
* @tangent: (out caller-allocates): Return location for
|
||||
* the tangent at the point
|
||||
*
|
||||
* Gets the tangent of the path at the point.
|
||||
*
|
||||
* Note that certain points on a path may not have a single
|
||||
* tangent, such as sharp turns. At such points, there are
|
||||
* two tangents -- the direction of the path going into the
|
||||
* point, and the direction coming out of it.
|
||||
*
|
||||
* The @direction argument lets you choose which one to get.
|
||||
*/
|
||||
void
|
||||
gsk_path_point_get_tangent (GskPathPoint *self,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
gsk_contour_get_point (self->contour,
|
||||
self->measure_data,
|
||||
self->offset,
|
||||
direction,
|
||||
NULL, tangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_point_get_curvature:
|
||||
* @self: a `GskPathPoint`
|
||||
* @center: (out caller-allocates): Return location for
|
||||
* the center of the osculating circle
|
||||
*
|
||||
* Calculates the curvature at the point @distance units into
|
||||
* the path.
|
||||
*
|
||||
* Optionally, returns the center of the osculating circle as well.
|
||||
*
|
||||
* If the curvature is infinite (at line segments), zero is returned,
|
||||
* and @center is not modified.
|
||||
*
|
||||
* Returns: The curvature of the path at the given point
|
||||
*/
|
||||
float
|
||||
gsk_path_point_get_curvature (GskPathPoint *self,
|
||||
graphene_point_t *center)
|
||||
{
|
||||
return gsk_contour_get_curvature (self->contour,
|
||||
self->measure_data,
|
||||
self->offset,
|
||||
center);
|
||||
}
|
29
gsk/gskpathpoint.h
Normal file
29
gsk/gskpathpoint.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_POINT (gsk_path_point_get_type ())
|
||||
|
||||
GType gsk_path_point_get_type (void) G_GNUC_CONST;
|
||||
GskPathPoint * gsk_path_point_ref (GskPathPoint *self);
|
||||
void gsk_path_point_unref (GskPathPoint *self);
|
||||
GskPathMeasure * gsk_path_point_get_measure (GskPathPoint *self);
|
||||
float gsk_path_point_get_distance (GskPathPoint *self);
|
||||
void gsk_path_point_get_position (GskPathPoint *self,
|
||||
graphene_point_t *position);
|
||||
void gsk_path_point_get_tangent (GskPathPoint *self,
|
||||
GskPathDirection direction,
|
||||
graphene_vec2_t *tangent);
|
||||
float gsk_path_point_get_curvature (GskPathPoint *self,
|
||||
graphene_point_t *center);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathPoint, gsk_path_point_unref)
|
||||
|
||||
G_END_DECLS
|
16
gsk/gskpathpointprivate.h
Normal file
16
gsk/gskpathpointprivate.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "gskpathpoint.h"
|
||||
#include "gskcontourprivate.h"
|
||||
#include "gskpathmeasure.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GskPathPoint * gsk_path_point_new (GskPathMeasure *measure,
|
||||
const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float contour_offset,
|
||||
float offset);
|
||||
|
||||
G_END_DECLS
|
||||
|
77
gsk/gskpathprivate.h
Normal file
77
gsk/gskpathprivate.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
|
||||
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
|
||||
|
||||
GskPath * gsk_path_new_from_contours (const GSList *contours);
|
||||
|
||||
gsize gsk_path_get_n_contours (GskPath *path);
|
||||
const GskContour * gsk_path_get_contour (GskPath *path,
|
||||
gsize i);
|
||||
|
||||
GskPathFlags gsk_path_get_flags (GskPath *self);
|
||||
|
||||
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
double tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
void gsk_path_builder_add_contour (GskPathBuilder *builder,
|
||||
GskContour *contour);
|
||||
|
||||
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
|
||||
float rx,
|
||||
float ry,
|
||||
float x_axis_rotation,
|
||||
gboolean large_arc,
|
||||
gboolean positive_sweep,
|
||||
float x,
|
||||
float y);
|
||||
|
||||
|
||||
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
|
||||
|
208
gsk/gskspline.c
Normal file
208
gsk/gskspline.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright © 2002 University of Southern California
|
||||
* 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
* Carl D. Worth <cworth@cworth.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gsksplineprivate.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* Spline deviation from the circle in radius would be given by:
|
||||
|
||||
error = sqrt (x**2 + y**2) - 1
|
||||
|
||||
A simpler error function to work with is:
|
||||
|
||||
e = x**2 + y**2 - 1
|
||||
|
||||
From "Good approximation of circles by curvature-continuous Bezier
|
||||
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
|
||||
Design 8 (1990) 22-41, we learn:
|
||||
|
||||
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
|
||||
|
||||
and
|
||||
abs (error) =~ 1/2 * e
|
||||
|
||||
Of course, this error value applies only for the particular spline
|
||||
approximation that is used in _cairo_gstate_arc_segment.
|
||||
*/
|
||||
static float
|
||||
arc_error_normalized (float angle)
|
||||
{
|
||||
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
|
||||
}
|
||||
|
||||
static float
|
||||
arc_max_angle_for_tolerance_normalized (float tolerance)
|
||||
{
|
||||
float angle, error;
|
||||
guint i;
|
||||
|
||||
/* Use table lookup to reduce search time in most cases. */
|
||||
struct {
|
||||
float angle;
|
||||
float error;
|
||||
} table[] = {
|
||||
{ G_PI / 1.0, 0.0185185185185185036127 },
|
||||
{ G_PI / 2.0, 0.000272567143730179811158 },
|
||||
{ G_PI / 3.0, 2.38647043651461047433e-05 },
|
||||
{ G_PI / 4.0, 4.2455377443222443279e-06 },
|
||||
{ G_PI / 5.0, 1.11281001494389081528e-06 },
|
||||
{ G_PI / 6.0, 3.72662000942734705475e-07 },
|
||||
{ G_PI / 7.0, 1.47783685574284411325e-07 },
|
||||
{ G_PI / 8.0, 6.63240432022601149057e-08 },
|
||||
{ G_PI / 9.0, 3.2715520137536980553e-08 },
|
||||
{ G_PI / 10.0, 1.73863223499021216974e-08 },
|
||||
{ G_PI / 11.0, 9.81410988043554039085e-09 },
|
||||
};
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (table); i++)
|
||||
{
|
||||
if (table[i].error < tolerance)
|
||||
return table[i].angle;
|
||||
}
|
||||
|
||||
i++;
|
||||
do {
|
||||
angle = G_PI / i++;
|
||||
error = arc_error_normalized (angle);
|
||||
} while (error > tolerance);
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
static guint
|
||||
arc_segments_needed (float angle,
|
||||
float radius,
|
||||
float tolerance)
|
||||
{
|
||||
float max_angle;
|
||||
|
||||
/* the error is amplified by at most the length of the
|
||||
* major axis of the circle; see cairo-pen.c for a more detailed analysis
|
||||
* of this. */
|
||||
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
|
||||
|
||||
return ceil (fabs (angle) / max_angle);
|
||||
}
|
||||
|
||||
/* We want to draw a single spline approximating a circular arc radius
|
||||
R from angle A to angle B. Since we want a symmetric spline that
|
||||
matches the endpoints of the arc in position and slope, we know
|
||||
that the spline control points must be:
|
||||
|
||||
(R * cos(A), R * sin(A))
|
||||
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
|
||||
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
|
||||
(R * cos(B), R * sin(B))
|
||||
|
||||
for some value of h.
|
||||
|
||||
"Approximation of circular arcs by cubic polynomials", Michael
|
||||
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
|
||||
various values of h along with error analysis for each.
|
||||
|
||||
From that paper, a very practical value of h is:
|
||||
|
||||
h = 4/3 * R * tan(angle/4)
|
||||
|
||||
This value does not give the spline with minimal error, but it does
|
||||
provide a very good approximation, (6th-order convergence), and the
|
||||
error expression is quite simple, (see the comment for
|
||||
_arc_error_normalized).
|
||||
*/
|
||||
static gboolean
|
||||
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
|
||||
float radius,
|
||||
float angle_A,
|
||||
float angle_B,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float r_sin_A, r_cos_A;
|
||||
float r_sin_B, r_cos_B;
|
||||
float h;
|
||||
|
||||
r_sin_A = radius * sin (angle_A);
|
||||
r_cos_A = radius * cos (angle_A);
|
||||
r_sin_B = radius * sin (angle_B);
|
||||
r_cos_B = radius * cos (angle_B);
|
||||
|
||||
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
|
||||
|
||||
return curve_func ((graphene_point_t[4]) {
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A,
|
||||
center->y + r_sin_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A - h * r_sin_A,
|
||||
center->y + r_sin_A + h * r_cos_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B + h * r_sin_B,
|
||||
center->y + r_sin_B - h * r_cos_B
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B,
|
||||
center->y + r_sin_B
|
||||
)
|
||||
},
|
||||
user_data);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float step = start_angle - end_angle;
|
||||
guint i, n_segments;
|
||||
|
||||
/* Recurse if drawing arc larger than pi */
|
||||
if (ABS (step) > G_PI)
|
||||
{
|
||||
float mid_angle = (start_angle + end_angle) / 2.0;
|
||||
|
||||
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
|
||||
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
else if (ABS (step) < tolerance)
|
||||
{
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
||||
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
|
||||
step = (end_angle - start_angle) / n_segments;
|
||||
|
||||
for (i = 0; i < n_segments - 1; i++, start_angle += step)
|
||||
{
|
||||
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
41
gsk/gsksplineprivate.h
Normal file
41
gsk/gsksplineprivate.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_SPLINE_PRIVATE_H__
|
||||
#define __GSK_SPLINE_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
|
||||
gpointer user_data);
|
||||
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_SPLINE_PRIVATE_H__ */
|
||||
|
@@ -25,6 +25,10 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include <gsk/gskenums.h>
|
||||
|
||||
typedef struct _GskPath GskPath;
|
||||
typedef struct _GskPathBuilder GskPathBuilder;
|
||||
typedef struct _GskPathMeasure GskPathMeasure;
|
||||
typedef struct _GskPathPoint GskPathPoint;
|
||||
typedef struct _GskRenderer GskRenderer;
|
||||
typedef struct _GskTransform GskTransform;
|
||||
|
||||
|
@@ -20,11 +20,14 @@ 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([
|
||||
'gskdiff.c',
|
||||
'gskcairorenderer.c',
|
||||
'gskdiff.c',
|
||||
'gskglshader.c',
|
||||
'gskrenderer.c',
|
||||
'gskrendernode.c',
|
||||
@@ -37,15 +40,25 @@ gsk_public_sources = files([
|
||||
|
||||
gsk_private_sources = files([
|
||||
'gskcairoblur.c',
|
||||
'gskcontour.c',
|
||||
'gskcurve.c',
|
||||
'gskcurveintersect.c',
|
||||
'gskdebug.c',
|
||||
'gskpath.c',
|
||||
'gskpathbuilder.c',
|
||||
'gskpathmeasure.c',
|
||||
'gskpathops.c',
|
||||
'gskpathpoint.c',
|
||||
'gskprivate.c',
|
||||
'gskprofiler.c',
|
||||
'gskspline.c',
|
||||
'gl/gskglattachmentstate.c',
|
||||
'gl/gskglbuffer.c',
|
||||
'gl/gskglcommandqueue.c',
|
||||
'gl/gskglcompiler.c',
|
||||
'gl/gskgldriver.c',
|
||||
'gl/gskglglyphlibrary.c',
|
||||
'gl/gskglglyphylibrary.c',
|
||||
'gl/gskgliconlibrary.c',
|
||||
'gl/gskglprogram.c',
|
||||
'gl/gskglrenderjob.c',
|
||||
@@ -185,6 +198,7 @@ gsk_deps = [
|
||||
cairo_csi_dep,
|
||||
pixbuf_dep,
|
||||
libgdk_dep,
|
||||
libglyphy_dep
|
||||
]
|
||||
|
||||
libgsk_f16c = static_library('gsk_f16c',
|
||||
|
@@ -1015,6 +1015,7 @@ gtk_deps = [
|
||||
epoxy_dep,
|
||||
libm,
|
||||
graphene_dep,
|
||||
libglyphy_dep,
|
||||
]
|
||||
|
||||
if x11_enabled
|
||||
|
@@ -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'
|
||||
@@ -25,6 +25,7 @@ epoxy_req = '>= 1.4'
|
||||
cloudproviders_req = '>= 0.3.1'
|
||||
xkbcommon_req = '>= 0.2.0'
|
||||
sysprof_req = '>= 3.38.0'
|
||||
libglyphy_req = '>= 0.2.0'
|
||||
|
||||
fs = import('fs')
|
||||
gnome = import('gnome')
|
||||
@@ -378,6 +379,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
6
subprojects/glyphy.wrap
Normal 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
123
tests/bigfont.c
Normal 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;
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
gtk_tests = [
|
||||
# testname, optional extra sources
|
||||
['movingtext'],
|
||||
['bigfont'],
|
||||
['testsections'],
|
||||
['testfilelauncher'],
|
||||
['input'],
|
||||
|
210
tests/movingtext.c
Normal file
210
tests/movingtext.c
Normal 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;
|
||||
}
|
164
testsuite/gsk/curve-special-cases.c
Normal file
164
testsuite/gsk/curve-special-cases.c
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static gboolean
|
||||
measure_segment (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_t,
|
||||
float to_t,
|
||||
GskCurveLineReason reason,
|
||||
gpointer data)
|
||||
{
|
||||
float *length = data;
|
||||
|
||||
*length += graphene_point_distance (from, to, NULL, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static float
|
||||
measure_length (const GskCurve *curve)
|
||||
{
|
||||
float result = 0;
|
||||
|
||||
gsk_curve_decompose (curve, 0.5, measure_segment, &result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* This is a pretty nasty conic that makes it obvious that split()
|
||||
* does not respect the progress values, so split() twice with
|
||||
* scaled factor won't work.
|
||||
*/
|
||||
static void
|
||||
test_conic_segment (void)
|
||||
{
|
||||
GskCurve c, s, e, m;
|
||||
graphene_point_t pts[4] = {
|
||||
GRAPHENE_POINT_INIT (-1856.131591796875, 46.217609405517578125),
|
||||
GRAPHENE_POINT_INIT (-1555.9866943359375, 966.0810546875),
|
||||
GRAPHENE_POINT_INIT (98.94945526123046875, 0),
|
||||
GRAPHENE_POINT_INIT (-1471.33154296875, 526.701171875)
|
||||
};
|
||||
float start = 0.02222645096480846405029296875;
|
||||
float end = 0.982032716274261474609375;
|
||||
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, pts));
|
||||
|
||||
gsk_curve_split (&c, start, &s, NULL);
|
||||
gsk_curve_segment (&c, start, end, &m);
|
||||
gsk_curve_split (&c, end, NULL, &e);
|
||||
|
||||
g_assert_cmpfloat_with_epsilon (measure_length (&c), measure_length (&s) + measure_length (&m) + measure_length (&e), 0.03125);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 100, 0);
|
||||
p[2] = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 20), 0);
|
||||
graphene_point_init (&p[3], 100, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 100, 50);
|
||||
graphene_point_init (&p[3], 100, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_degenerate_tangents (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 0);
|
||||
graphene_point_init (&p[2], 100, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 50, 0);
|
||||
graphene_point_init (&p[2], 50, 0);
|
||||
graphene_point_init (&p[3], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
gsk_curve_get_end_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/special/conic-segment", test_conic_segment);
|
||||
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
423
testsuite/gsk/curve.c
Normal file
423
testsuite/gsk/curve.c
Normal file
@@ -0,0 +1,423 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
#include "gsk/gskpath.h"
|
||||
#include "gsk/gskpathmeasure.h"
|
||||
|
||||
static void
|
||||
init_random_point (graphene_point_t *p)
|
||||
{
|
||||
p->x = g_test_rand_double_range (0, 1000);
|
||||
p->y = g_test_rand_double_range (0, 1000);
|
||||
}
|
||||
|
||||
static float
|
||||
random_weight (void)
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
return g_test_rand_double_range (1, 20);
|
||||
else
|
||||
return 1.0 / g_test_rand_double_range (1, 20);
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve_with_op (GskCurve *curve,
|
||||
GskPathOperation min_op,
|
||||
GskPathOperation max_op)
|
||||
{
|
||||
switch (g_test_rand_int_range (min_op, max_op + 1))
|
||||
{
|
||||
case GSK_PATH_LINE:
|
||||
{
|
||||
graphene_point_t p[2];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
graphene_point_t p[3];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
init_random_point (&p[3]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
p[2] = GRAPHENE_POINT_INIT (random_weight(), 0);
|
||||
init_random_point (&p[3]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, p));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
init_random_curve (GskCurve *curve)
|
||||
{
|
||||
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CONIC);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_vec2_t vec, exact;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_get_tangent (&c, 0, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_start_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
|
||||
gsk_curve_get_tangent (&c, 1, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_end_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_points (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
/* We can assert equality here because evaluating the polynomials with 0
|
||||
* has no effect on accuracy.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 0, &p);
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
|
||||
/* But here we evaluate the polynomials with 1 which gives the highest possible
|
||||
* accuracy error. So we'll just be generous here.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 1, &p);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
/* at this point the subdivision stops and the decomposer
|
||||
* violates tolerance rules
|
||||
*/
|
||||
#define MIN_PROGRESS (1/1024.f)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
graphene_point_t p;
|
||||
float t;
|
||||
} PointOnLine;
|
||||
|
||||
static gboolean
|
||||
add_line_to_array (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
|
||||
|
||||
g_assert_true (array->len > 0);
|
||||
g_assert_cmpfloat (from_progress, >=, 0.0f);
|
||||
g_assert_cmpfloat (from_progress, <, to_progress);
|
||||
g_assert_cmpfloat (to_progress, <=, 1.0f);
|
||||
|
||||
g_assert_true (graphene_point_equal (&last->p, from));
|
||||
g_assert_cmpfloat (last->t, ==, from_progress);
|
||||
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GArray *array;
|
||||
GskCurve c;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
|
||||
|
||||
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
|
||||
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
|
||||
graphene_point_t p;
|
||||
|
||||
/* Check that the points we got are actually on the line */
|
||||
gsk_curve_get_point (&c, pol->t, &p);
|
||||
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
|
||||
|
||||
/* Check that the mid point is not further than the tolerance */
|
||||
if (j > 0)
|
||||
{
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
|
||||
graphene_point_t mid;
|
||||
|
||||
if (pol->t - last->t > MIN_PROGRESS)
|
||||
{
|
||||
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
|
||||
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
|
||||
/* The decomposer does this cheaper Manhattan distance test,
|
||||
* so graphene_point_near() does not work */
|
||||
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
|
||||
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
add_curve_to_array (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
|
||||
g_array_append_val (array, c);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_conic (void)
|
||||
{
|
||||
g_test_skip ("No good error bounds for decomposing conics");
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GArray *array;
|
||||
GskCurve c;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
const graphene_point_t *s;
|
||||
|
||||
init_random_curve_with_op (&c, GSK_PATH_CONIC, GSK_PATH_CONIC);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
s = gsk_curve_get_start_point (&c);
|
||||
gsk_path_builder_move_to (builder, s->x, s->y);
|
||||
gsk_curve_builder_to (&c, builder);
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
|
||||
|
||||
g_assert_true (gsk_curve_decompose_curve (&c, GSK_PATH_FOREACH_ALLOW_CUBIC, 0.1, add_curve_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 1);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
GskCurve *c2 = &g_array_index (array, GskCurve, j);
|
||||
|
||||
g_assert_true (c2->op == GSK_PATH_CUBIC);
|
||||
|
||||
/* Check that the curves we got are approximating the conic */
|
||||
for (int k = 0; k < 11; k++)
|
||||
{
|
||||
GskPathPoint *point;
|
||||
graphene_point_t p, q;
|
||||
|
||||
gsk_curve_get_point (c2, k/10.0, &p);
|
||||
point = gsk_path_measure_get_closest_point (measure, &p, INFINITY);
|
||||
gsk_path_point_get_position (point, &q);
|
||||
g_assert_true (graphene_point_near (&p, &q, 0.5));
|
||||
gsk_path_point_unref (point);
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into (GskPathForeachFlags flags)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskPathBuilder *builder;
|
||||
const graphene_point_t *s;
|
||||
GskPath *path;
|
||||
GArray *array;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
s = gsk_curve_get_start_point (&c);
|
||||
gsk_path_builder_move_to (builder, s->x, s->y);
|
||||
gsk_curve_builder_to (&c, builder);
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
|
||||
|
||||
g_assert_true (gsk_curve_decompose_curve (&c, flags, 0.1, add_curve_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 1);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
GskCurve *c2 = &g_array_index (array, GskCurve, j);
|
||||
|
||||
switch (c2->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
break;
|
||||
case GSK_PATH_QUAD:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
break;
|
||||
case GSK_PATH_CUBIC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
break;
|
||||
case GSK_PATH_CONIC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CONIC);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (array);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_line (void)
|
||||
{
|
||||
test_curve_decompose_into (0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_quad (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_QUAD);
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose_into_cubic (void)
|
||||
{
|
||||
test_curve_decompose_into (GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
}
|
||||
|
||||
/* Some sanity checks for splitting curves. */
|
||||
static void
|
||||
test_curve_split (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p;
|
||||
graphene_vec2_t t, t1, t2;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_split (&c, 0.5, &c1, &c2);
|
||||
|
||||
g_assert_true (c1.op == c.op);
|
||||
g_assert_true (c2.op == c.op);
|
||||
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c),
|
||||
gsk_curve_get_start_point (&c1), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1),
|
||||
gsk_curve_get_start_point (&c2), 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c),
|
||||
gsk_curve_get_end_point (&c2), 0.005));
|
||||
gsk_curve_get_point (&c, 0.5, &p);
|
||||
gsk_curve_get_tangent (&c, 0.5, &t);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1), &p, 0.005));
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c2), &p, 0.005));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t1);
|
||||
gsk_curve_get_start_tangent (&c1, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c1, &t1);
|
||||
gsk_curve_get_start_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t1, 0.005));
|
||||
g_assert_true (graphene_vec2_near (&t, &t2, 0.005));
|
||||
gsk_curve_get_end_tangent (&c, &t1);
|
||||
gsk_curve_get_end_tangent (&c2, &t2);
|
||||
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/curve/points", test_curve_points);
|
||||
g_test_add_func ("/curve/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/decompose", test_curve_decompose);
|
||||
g_test_add_func ("/curve/decompose/conic", test_curve_decompose_conic);
|
||||
g_test_add_func ("/curve/decompose/into/line", test_curve_decompose_into_line);
|
||||
g_test_add_func ("/curve/decompose/into/quad", test_curve_decompose_into_quad);
|
||||
g_test_add_func ("/curve/decompose/into/cubic", test_curve_decompose_into_cubic);
|
||||
g_test_add_func ("/curve/split", test_curve_split);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
252
testsuite/gsk/measure-special-cases.c
Normal file
252
testsuite/gsk/measure-special-cases.c
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskpath.h"
|
||||
#include "gsk/gskpathbuilder.h"
|
||||
#include "gsk/gskpathmeasure.h"
|
||||
#include "gsk/gskpathpoint.h"
|
||||
|
||||
static void
|
||||
test_bad_split (void)
|
||||
{
|
||||
GskPath *path, *path1;
|
||||
GskPathMeasure *measure, *measure1;
|
||||
GskPathBuilder *builder;
|
||||
float split, length, epsilon;
|
||||
|
||||
/* An example that was isolated from the /path/segment test path.c
|
||||
* It shows how uneven parametrization of cubics can lead to bad
|
||||
* lengths reported by the measure.
|
||||
*/
|
||||
|
||||
path = gsk_path_parse ("M 0 0 C 2 0 4 0 4 0");
|
||||
|
||||
measure = gsk_path_measure_new (path);
|
||||
split = 2.962588;
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
epsilon = MAX (length / 256, 1.f / 1024);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, 0, split);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
measure1 = gsk_path_measure_new (path1);
|
||||
|
||||
g_assert_cmpfloat_with_epsilon (split, gsk_path_measure_get_length (measure1), epsilon);
|
||||
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_bad_in_fill (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
gboolean inside;
|
||||
|
||||
/* A fat Cantarell W */
|
||||
path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
|
||||
|
||||
measure = gsk_path_measure_new (path);
|
||||
|
||||
/* The midpoint of the right foot of a fat Cantarell X */
|
||||
inside = gsk_path_measure_in_fill (measure, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
|
||||
|
||||
g_assert_false (inside);
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rect (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
GskPathPoint *point;
|
||||
graphene_point_t p;
|
||||
graphene_vec2_t tangent, expected_tangent;
|
||||
float length;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 50));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 300);
|
||||
|
||||
#define TEST_POS_AT(distance, X, Y) \
|
||||
point = gsk_path_measure_get_point (measure, distance); \
|
||||
gsk_path_point_get_position (point, &p); \
|
||||
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||
gsk_path_point_unref (point); \
|
||||
point = gsk_path_measure_get_closest_point (measure, &GRAPHENE_POINT_INIT (X, Y), INFINITY); \
|
||||
if (distance < length) \
|
||||
g_assert_true (fabs (gsk_path_point_get_distance (point) - distance) < 0.01); \
|
||||
else \
|
||||
g_assert_true (fabs (gsk_path_point_get_distance (point)) < 0.01); \
|
||||
gsk_path_point_get_position (point, &p); \
|
||||
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \
|
||||
point = gsk_path_measure_get_point (measure, distance); \
|
||||
gsk_path_point_get_tangent (point, GSK_PATH_START, &tangent); \
|
||||
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x1, y1), 0.01)); \
|
||||
gsk_path_point_get_tangent (point, GSK_PATH_END, &tangent); \
|
||||
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \
|
||||
gsk_path_point_unref (point); \
|
||||
|
||||
#define TEST_POS_AT2(distance, X, Y, expected_distance) \
|
||||
point = gsk_path_measure_get_point (measure, distance); \
|
||||
gsk_path_point_get_position (point, &p); \
|
||||
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||
gsk_path_point_unref (point); \
|
||||
point = gsk_path_measure_get_closest_point (measure, &GRAPHENE_POINT_INIT (X, Y), INFINITY); \
|
||||
g_assert_true (fabs (gsk_path_point_get_distance (point) - expected_distance) < 0.01); \
|
||||
gsk_path_point_get_position (point, &p); \
|
||||
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
TEST_POS_AT (0, 0, 0)
|
||||
TEST_POS_AT (25, 25, 0)
|
||||
TEST_POS_AT (100, 100, 0)
|
||||
TEST_POS_AT (110, 100, 10)
|
||||
TEST_POS_AT (150, 100, 50)
|
||||
TEST_POS_AT (175, 75, 50)
|
||||
TEST_POS_AT (250, 0, 50)
|
||||
TEST_POS_AT (260, 0, 40)
|
||||
TEST_POS_AT (300, 0, 0)
|
||||
|
||||
TEST_TANGENT_AT (0, 0, -1, 1, 0)
|
||||
TEST_TANGENT_AT (50, 1, 0, 1, 0)
|
||||
TEST_TANGENT_AT (100, 1, 0, 0, 1)
|
||||
TEST_TANGENT_AT (125, 0, 1, 0, 1)
|
||||
TEST_TANGENT_AT (150, 0, 1, -1, 0)
|
||||
TEST_TANGENT_AT (200, -1, 0, -1, 0)
|
||||
TEST_TANGENT_AT (250, -1, 0, 0, -1)
|
||||
TEST_TANGENT_AT (275, 0, -1, 0, -1)
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 50, -100, -50));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 300);
|
||||
|
||||
TEST_POS_AT (0, 100, 50)
|
||||
TEST_POS_AT (25, 75, 50)
|
||||
TEST_POS_AT (100, 0, 50)
|
||||
TEST_POS_AT (110, 0, 40)
|
||||
TEST_POS_AT (150, 0, 0)
|
||||
TEST_POS_AT (175, 25, 0)
|
||||
TEST_POS_AT (250, 100, 0)
|
||||
TEST_POS_AT (260, 100, 10)
|
||||
TEST_POS_AT (300, 100, 50)
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 50));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 300);
|
||||
|
||||
TEST_POS_AT (0, 100, 0)
|
||||
TEST_POS_AT (25, 75, 0)
|
||||
TEST_POS_AT (100, 0, 0)
|
||||
TEST_POS_AT (110, 0, 10)
|
||||
TEST_POS_AT (150, 0, 50)
|
||||
TEST_POS_AT (175, 25, 50)
|
||||
TEST_POS_AT (250, 100, 50)
|
||||
TEST_POS_AT (260, 100, 40)
|
||||
TEST_POS_AT (300, 100, 0)
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 0));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 200);
|
||||
|
||||
TEST_POS_AT2 (0, 0, 0, 0)
|
||||
TEST_POS_AT2 (25, 25, 0, 25)
|
||||
TEST_POS_AT2 (100, 100, 0, 100)
|
||||
TEST_POS_AT2 (110, 90, 0, 90)
|
||||
TEST_POS_AT2 (200, 0, 0, 0)
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 0));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 200);
|
||||
|
||||
TEST_POS_AT2 (0, 100, 0, 0)
|
||||
TEST_POS_AT2 (25, 75, 0, 25)
|
||||
TEST_POS_AT2 (100, 0, 0, 100)
|
||||
TEST_POS_AT2 (110, 10, 0, 90)
|
||||
TEST_POS_AT2 (200, 100, 0, 0)
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 100, 0, -100));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
g_assert_true (length == 200);
|
||||
|
||||
TEST_POS_AT2 (0, 0, 100, 0)
|
||||
TEST_POS_AT2 (25, 0, 75, 25)
|
||||
TEST_POS_AT2 (100, 0, 0, 100)
|
||||
TEST_POS_AT2 (110, 0, 10, 90)
|
||||
TEST_POS_AT2 (200, 0, 100, 0)
|
||||
|
||||
#undef TEST_POS_AT
|
||||
#undef TEST_POS_AT2
|
||||
#undef TEST_TANGENT_AT
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/measure/bad-split", test_bad_split);
|
||||
g_test_add_func ("/measure/bad-in-fill", test_bad_in_fill);
|
||||
g_test_add_func ("/measure/rect", test_rect);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
910
testsuite/gsk/measure.c
Normal file
910
testsuite/gsk/measure.c
Normal file
@@ -0,0 +1,910 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskpath.h"
|
||||
#include "gsk/gskpathbuilder.h"
|
||||
#include "gsk/gskpathmeasure.h"
|
||||
|
||||
static float
|
||||
random_weight (void)
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
return g_test_rand_double_range (1, 20);
|
||||
else
|
||||
return 1.0 / g_test_rand_double_range (1, 20);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_degenerate_path (guint max_contours)
|
||||
{
|
||||
#define N_DEGENERATE_PATHS 15
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
|
||||
{
|
||||
case 0:
|
||||
/* empty path */
|
||||
break;
|
||||
|
||||
case 1:
|
||||
/* a single point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
/* N points */
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
/* 1 closed point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
/* the same point closed N times */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
/* a zero-width and zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0, 0));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
/* a zero-width rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0,
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 7:
|
||||
/* a zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0));
|
||||
break;
|
||||
|
||||
case 8:
|
||||
/* a negative-size rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 0),
|
||||
g_test_rand_double_range (-1000, 0)));
|
||||
break;
|
||||
|
||||
case 9:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 10:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 11:
|
||||
/* an absolutely random circle */
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 12:
|
||||
/* a zero-length line */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_line_to (builder, point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
/* a curve with start == end */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 14:
|
||||
/* a conic with start == end */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
point.x, point.y,
|
||||
random_weight ());
|
||||
}
|
||||
break;
|
||||
|
||||
case N_DEGENERATE_PATHS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours);
|
||||
|
||||
static void
|
||||
add_shape_contour (GskPathBuilder *builder)
|
||||
{
|
||||
#define N_SHAPE_CONTOURS 3
|
||||
switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (1, 1000),
|
||||
g_test_rand_double_range (1, 1000)));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
GskPath *path = create_random_path (1);
|
||||
gsk_path_builder_add_path (builder, path);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
break;
|
||||
|
||||
case N_SHAPE_CONTOURS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_standard_contour (GskPathBuilder *builder)
|
||||
{
|
||||
guint i, n;
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
else
|
||||
gsk_path_builder_rel_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
|
||||
/* that 20 is random, but should be enough to get some
|
||||
* crazy self-intersecting shapes */
|
||||
n = g_test_rand_int_range (1, 20);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
switch (g_test_rand_int_range (0, 8))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_rel_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
gsk_path_builder_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
gsk_path_builder_rel_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
gsk_path_builder_rel_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
gsk_path_builder_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
random_weight ());
|
||||
break;
|
||||
|
||||
case 7:
|
||||
gsk_path_builder_rel_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
random_weight ());
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
guint i, n;
|
||||
|
||||
/* 5% chance for a weird shape */
|
||||
if (!g_test_rand_int_range (0, 20))
|
||||
return create_random_degenerate_path (max_contours);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
n = g_test_rand_int_range (1, 10);
|
||||
n = MIN (n, max_contours);
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
/* 2/3 of shapes are standard contours */
|
||||
if (g_test_rand_int_range (0, 3))
|
||||
add_standard_contour (builder);
|
||||
else
|
||||
add_shape_contour (builder);
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GskPathOperation op;
|
||||
graphene_point_t pts[4];
|
||||
float weight;
|
||||
} PathOperation;
|
||||
|
||||
static void
|
||||
test_segment_start (void)
|
||||
{
|
||||
GskPath *path, *path1;
|
||||
GskPathMeasure *measure, *measure1;
|
||||
GskPathBuilder *builder;
|
||||
float epsilon, length;
|
||||
guint i;
|
||||
|
||||
path = create_random_path (G_MAXUINT);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
float seg_length = length * i / 100.0f;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, 0, seg_length);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
measure1 = gsk_path_measure_new (path1);
|
||||
|
||||
if (seg_length == 0)
|
||||
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
|
||||
else
|
||||
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
|
||||
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_segment_end (void)
|
||||
{
|
||||
GskPath *path, *path1;
|
||||
GskPathMeasure *measure, *measure1;
|
||||
GskPathBuilder *builder;
|
||||
float epsilon, length;
|
||||
guint i;
|
||||
|
||||
path = create_random_path (G_MAXUINT);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
float seg_length = length * i / 100.0f;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, length - seg_length, length);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
measure1 = gsk_path_measure_new (path1);
|
||||
|
||||
if (seg_length == 0)
|
||||
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
|
||||
else
|
||||
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
|
||||
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_segment_chunk (void)
|
||||
{
|
||||
GskPath *path, *path1, *path2;
|
||||
GskPathMeasure *measure, *measure1, *measure2;
|
||||
GskPathBuilder *builder;
|
||||
float epsilon, length;
|
||||
guint i;
|
||||
|
||||
path = create_random_path (G_MAXUINT);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||
|
||||
for (i = 0; i <= 100; i++)
|
||||
{
|
||||
float seg_start = length * i / 200.0f;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, seg_start, seg_start + length / 2);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
measure1 = gsk_path_measure_new (path1);
|
||||
|
||||
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure1), epsilon);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, seg_start + length / 2, seg_start);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
measure2 = gsk_path_measure_new (path2);
|
||||
|
||||
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure2), epsilon);
|
||||
|
||||
gsk_path_measure_unref (measure2);
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
test_segment (void)
|
||||
{
|
||||
GskPath *path, *path1, *path2, *path3;
|
||||
GskPathMeasure *measure, *measure1, *measure2, *measure3;
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
float split1, split2, epsilon, length;
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
path = create_random_path (G_MAXUINT);
|
||||
measure = gsk_path_measure_new (path);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
/* chosen high enough to stop the testsuite from failing */
|
||||
epsilon = MAX (length / 64, 1.f / 1024);
|
||||
|
||||
split1 = g_test_rand_double_range (0, length);
|
||||
split2 = g_test_rand_double_range (split1, length);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, 0, split1);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
measure1 = gsk_path_measure_new (path1);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, split1, split2);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
measure2 = gsk_path_measure_new (path2);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, measure, split2, length);
|
||||
path3 = gsk_path_builder_free_to_path (builder);
|
||||
measure3 = gsk_path_measure_new (path3);
|
||||
|
||||
g_assert_cmpfloat_with_epsilon (split1, gsk_path_measure_get_length (measure1), epsilon);
|
||||
g_assert_cmpfloat_with_epsilon (split2 - split1, gsk_path_measure_get_length (measure2), epsilon);
|
||||
g_assert_cmpfloat_with_epsilon (length - split2, gsk_path_measure_get_length (measure3), epsilon);
|
||||
|
||||
gsk_path_measure_unref (measure2);
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_get_point (void)
|
||||
{
|
||||
static const guint max_contours = 5;
|
||||
static const float tolerance = 1.0;
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
GskPathPoint *point;
|
||||
guint n_discontinuities;
|
||||
float length, offset, last_offset;
|
||||
graphene_point_t p, last_point;
|
||||
guint i, j;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
{
|
||||
path = create_random_path (max_contours);
|
||||
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
n_discontinuities = 0;
|
||||
|
||||
point = gsk_path_measure_get_point (measure, 0);
|
||||
if (point == NULL)
|
||||
{
|
||||
g_assert_true (gsk_path_is_empty (path));
|
||||
continue;
|
||||
}
|
||||
gsk_path_point_get_position (point, &last_point);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
/* FIXME: anything we can test with tangents here? */
|
||||
last_offset = 0;
|
||||
|
||||
for (j = 1; j <= 1024; j++)
|
||||
{
|
||||
offset = length * j / 1024;
|
||||
point = gsk_path_measure_get_point (measure, offset);
|
||||
gsk_path_point_get_position (point, &p);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
|
||||
{
|
||||
n_discontinuities++;
|
||||
g_assert_cmpint (n_discontinuities, <, max_contours);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
last_point = p;
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_closest_point (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
GskPath *path, *path1, *path2;
|
||||
GskPathMeasure *measure, *measure1, *measure2;
|
||||
GskPathBuilder *builder;
|
||||
GskPathPoint *point;
|
||||
guint i, j;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
{
|
||||
path1 = create_random_path (G_MAXUINT);
|
||||
measure1 = gsk_path_measure_new_with_tolerance (path1, tolerance);
|
||||
path2 = create_random_path (G_MAXUINT);
|
||||
measure2 = gsk_path_measure_new_with_tolerance (path2, tolerance);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_path (builder, path1);
|
||||
gsk_path_builder_add_path (builder, path2);
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
graphene_point_t p1, p2, p;
|
||||
graphene_vec2_t t1, t2, t;
|
||||
float offset1, offset2, offset;
|
||||
float distance1, distance2, distance;
|
||||
|
||||
offset1 = offset2 = offset = 0;
|
||||
distance1 = distance2 = distance = 0;
|
||||
point = gsk_path_measure_get_closest_point (measure1, &test, INFINITY);
|
||||
g_assert_true (point != NULL);
|
||||
|
||||
gsk_path_point_get_position (point, &p1);
|
||||
gsk_path_point_get_tangent (point, GSK_PATH_END, &t1);
|
||||
offset1 = gsk_path_point_get_distance (point);
|
||||
distance1 = graphene_point_distance (&p1, &test, NULL, NULL);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
point = gsk_path_measure_get_closest_point (measure2, &test, INFINITY);
|
||||
g_assert_true (point != NULL);
|
||||
|
||||
gsk_path_point_get_position (point, &p2);
|
||||
gsk_path_point_get_tangent (point, GSK_PATH_END, &t2);
|
||||
offset2 = gsk_path_point_get_distance (point);
|
||||
distance2 = graphene_point_distance (&p2, &test, NULL, NULL);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
point = gsk_path_measure_get_closest_point (measure, &test, INFINITY);
|
||||
g_assert_true (point != NULL);
|
||||
|
||||
gsk_path_point_get_position (point, &p);
|
||||
gsk_path_point_get_tangent (point, GSK_PATH_END, &t);
|
||||
offset = gsk_path_point_get_distance (point);
|
||||
distance = graphene_point_distance (&p, &test, NULL, NULL);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
if (distance1 < distance2 + tolerance)
|
||||
{
|
||||
g_assert_cmpfloat (distance1, ==, distance);
|
||||
g_assert_cmpfloat (p1.x, ==, p.x);
|
||||
g_assert_cmpfloat (p1.y, ==, p.y);
|
||||
g_assert_cmpfloat (offset1, ==, offset);
|
||||
g_assert_true (graphene_vec2_equal (&t1, &t));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert_cmpfloat (distance2, ==, distance);
|
||||
g_assert_cmpfloat (p2.x, ==, p.x);
|
||||
g_assert_cmpfloat (p2.y, ==, p.y);
|
||||
g_assert_cmpfloat_with_epsilon (offset2 + gsk_path_measure_get_length (measure1), offset, MAX (G_MINFLOAT, offset / 1024));
|
||||
g_assert_true (graphene_vec2_equal (&t2, &t));
|
||||
}
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure2);
|
||||
gsk_path_measure_unref (measure1);
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_closest_point_for_point (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure;
|
||||
GskPathPoint *point;
|
||||
float length, offset, closest_offset, distance;
|
||||
graphene_point_t p, closest_point;
|
||||
guint i, j;
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
path = create_random_path (G_MAXUINT);
|
||||
if (gsk_path_is_empty (path))
|
||||
{
|
||||
/* empty paths have no closest point to anything */
|
||||
gsk_path_unref (path);
|
||||
continue;
|
||||
}
|
||||
|
||||
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||
length = gsk_path_measure_get_length (measure);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
offset = g_test_rand_double_range (0, length);
|
||||
|
||||
point = gsk_path_measure_get_point (measure, offset);
|
||||
gsk_path_point_get_position (point, &p);
|
||||
gsk_path_point_unref (point);
|
||||
point = gsk_path_measure_get_closest_point (measure, &p, tolerance);
|
||||
g_assert_nonnull (point);
|
||||
gsk_path_point_get_position (point, &closest_point);
|
||||
closest_offset = gsk_path_point_get_distance (point);
|
||||
distance = graphene_point_distance (&p, &closest_point, NULL, NULL);
|
||||
gsk_path_point_unref (point);
|
||||
|
||||
/* should be given due to the TRUE return above, but who knows... */
|
||||
g_assert_cmpfloat (distance, <=, tolerance);
|
||||
g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, tolerance);
|
||||
/* can't do == here because points may overlap if we're unlucky */
|
||||
g_assert_cmpfloat (closest_offset, <, offset + tolerance);
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
}
|
||||
|
||||
#define N_PATHS 3
|
||||
static void
|
||||
test_in_fill_union (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathMeasure *measure, *measures[N_PATHS];
|
||||
GskPathBuilder *builder;
|
||||
guint i, j, k;
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
builder = gsk_path_builder_new ();
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
{
|
||||
path = create_random_path (G_MAXUINT);
|
||||
measures[k] = gsk_path_measure_new (path);
|
||||
gsk_path_builder_add_path (builder, path);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
measure = gsk_path_measure_new (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
GskFillRule fill_rule;
|
||||
|
||||
for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
|
||||
{
|
||||
guint n_in_fill = 0;
|
||||
gboolean in_fill;
|
||||
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
{
|
||||
if (gsk_path_measure_in_fill (measures[k], &test, GSK_FILL_RULE_EVEN_ODD))
|
||||
n_in_fill++;
|
||||
}
|
||||
|
||||
in_fill = gsk_path_measure_in_fill (measure, &test, GSK_FILL_RULE_EVEN_ODD);
|
||||
|
||||
switch (fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
if (n_in_fill == 0)
|
||||
g_assert_false (in_fill);
|
||||
else if (n_in_fill == 1)
|
||||
g_assert_true (in_fill);
|
||||
/* else we can't say anything because the winding rule doesn't give enough info */
|
||||
break;
|
||||
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
g_assert_cmpint (in_fill, ==, n_in_fill & 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measure);
|
||||
for (k = 0; k < N_PATHS; k++)
|
||||
{
|
||||
gsk_path_measure_unref (measures[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef N_PATHS
|
||||
|
||||
/* This is somewhat sucky because using foreach breaks up the contours
|
||||
* (like rects and circles) and replaces everything with the standard
|
||||
* contour.
|
||||
* But at least it extensively tests the standard contour.
|
||||
*/
|
||||
static gboolean
|
||||
rotate_path_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder **builders = user_data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
|
||||
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builders[0]);
|
||||
gsk_path_builder_close (builders[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_conic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
|
||||
gsk_path_builder_conic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, weight);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_in_fill_rotated (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathBuilder *builders[2];
|
||||
GskPathMeasure *measures[2];
|
||||
guint i, j;
|
||||
|
||||
#define N_FILL_RULES 2
|
||||
/* if this triggers, you added a new enum value to GskFillRule, so the define above needs
|
||||
* an update */
|
||||
//g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
|
||||
|
||||
for (i = 0; i < 100; i++)
|
||||
{
|
||||
path = create_random_path (G_MAXUINT);
|
||||
builders[0] = gsk_path_builder_new ();
|
||||
builders[1] = gsk_path_builder_new ();
|
||||
/* Use -1 here because we want all the flags, even future additions */
|
||||
gsk_path_foreach (path, -1, rotate_path_cb, builders);
|
||||
gsk_path_unref (path);
|
||||
|
||||
path = gsk_path_builder_free_to_path (builders[0]);
|
||||
measures[0] = gsk_path_measure_new (path);
|
||||
gsk_path_unref (path);
|
||||
path = gsk_path_builder_free_to_path (builders[1]);
|
||||
measures[1] = gsk_path_measure_new (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
for (j = 0; j < 100; j++)
|
||||
{
|
||||
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
|
||||
float x = g_test_rand_double_range (-1000, 1000);
|
||||
float y = g_test_rand_double_range (-1000, 1000);
|
||||
|
||||
g_assert_cmpint (gsk_path_measure_in_fill (measures[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
|
||||
==,
|
||||
gsk_path_measure_in_fill (measures[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
|
||||
g_assert_cmpint (gsk_path_measure_in_fill (measures[0], &GRAPHENE_POINT_INIT (y, x), fill_rule),
|
||||
==,
|
||||
gsk_path_measure_in_fill (measures[1], &GRAPHENE_POINT_INIT (x, -y), fill_rule));
|
||||
}
|
||||
|
||||
gsk_path_measure_unref (measures[0]);
|
||||
gsk_path_measure_unref (measures[1]);
|
||||
}
|
||||
#undef N_FILL_RULES
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/measure/segment_start", test_segment_start);
|
||||
g_test_add_func ("/measure/segment_end", test_segment_end);
|
||||
g_test_add_func ("/measure/segment_chunk", test_segment_chunk);
|
||||
g_test_add_func ("/measure/segment", test_segment);
|
||||
g_test_add_func ("/measure/get_point", test_get_point);
|
||||
g_test_add_func ("/measure/closest_point", test_closest_point);
|
||||
g_test_add_func ("/measure/closest_point_for_point", test_closest_point_for_point);
|
||||
g_test_add_func ("/measure/in-fill-union", test_in_fill_union);
|
||||
g_test_add_func ("/measure/in-fill-rotated", test_in_fill_rotated);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@@ -389,6 +389,12 @@ foreach t : tests
|
||||
endforeach
|
||||
|
||||
internal_tests = [
|
||||
['measure'],
|
||||
['measure-special-cases'],
|
||||
['path'],
|
||||
['path-special-cases'],
|
||||
[ 'curve' ],
|
||||
[ 'curve-special-cases' ],
|
||||
[ 'diff' ],
|
||||
[ 'half-float' ],
|
||||
['rounded-rect'],
|
||||
|
322
testsuite/gsk/path-special-cases.c
Normal file
322
testsuite/gsk/path-special-cases.c
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskpath.h"
|
||||
#include "gsk/gskpathbuilder.h"
|
||||
|
||||
/* testcases from path_parser.rs in librsvg */
|
||||
static void
|
||||
test_rsvg_parse (void)
|
||||
{
|
||||
struct {
|
||||
const char *in;
|
||||
const char *out;
|
||||
} tests[] = {
|
||||
{ "", "" },
|
||||
// numbers
|
||||
{ "M 10 20", "M 10 20" },
|
||||
{ "M -10 -20", "M -10 -20" },
|
||||
{ "M .10 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10 -0.20", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M10.5.50", "M 10.5 0.5" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1 .20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E1-.20", "M -1 -0.2" },
|
||||
{ "M10.10E2 -0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2-0.20e-3", "M -1010 -0.0002" },
|
||||
{ "M1e2.5", "M 100 0.5" },
|
||||
{ "M1e-2.5", "M 0.01 0.5" },
|
||||
{ "M1e+2.5", "M 100 0.5" },
|
||||
// bogus numbers
|
||||
{ "M+", NULL },
|
||||
{ "M-", NULL },
|
||||
{ "M+x", NULL },
|
||||
{ "M10e", NULL },
|
||||
{ "M10ex", NULL },
|
||||
{ "M10e-", NULL },
|
||||
{ "M10e+x", NULL },
|
||||
// numbers with comma
|
||||
{ "M 10, 20", "M 10 20" },
|
||||
{ "M -10,-20", "M -10 -20" },
|
||||
{ "M.10 , 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10, -0.20 ", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1,.20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E-2,-.20", "M -0.001 -0.2" },
|
||||
{ "M10.10E2,-0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2,-0.20e-3", "M -1010 -0.0002" },
|
||||
// single moveto
|
||||
{ "M 10 20 ", "M 10 20" },
|
||||
{ "M10,20 ", "M 10 20" },
|
||||
{ "M10 20 ", "M 10 20" },
|
||||
{ " M10,20 ", "M 10 20" },
|
||||
// relative moveto
|
||||
{ "m10 20", "M 10 20" },
|
||||
// absolute moveto with implicit lineto
|
||||
{ "M10 20 30 40", "M 10 20 L 30 40" },
|
||||
{ "M10,20,30,40", "M 10 20 L 30 40" },
|
||||
{ "M.1-2,3E2-4", "M 0.1 -2 L 300 -4" },
|
||||
// relative moveto with implicit lineto
|
||||
{ "m10 20 30 40", "M 10 20 L 40 60" },
|
||||
// relative moveto with relative lineto sequence
|
||||
{ "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12",
|
||||
"M 46 447 L 46 447.5 L 45 447.5 L 44 447.5 L 44 448.5 L 44 460.5" },
|
||||
// absolute moveto with implicit linetos
|
||||
{ "M10,20 30,40,50 60", "M 10 20 L 30 40 L 50 60" },
|
||||
// relative moveto with implicit linetos
|
||||
{ "m10 20 30 40 50 60", "M 10 20 L 40 60 L 90 120" },
|
||||
// absolute moveto moveto
|
||||
{ "M10 20 M 30 40", "M 10 20 M 30 40" },
|
||||
// relative moveto moveto
|
||||
{ "m10 20 m 30 40", "M 10 20 M 40 60" },
|
||||
// relative moveto lineto moveto
|
||||
{ "m10 20 30 40 m 50 60", "M 10 20 L 40 60 M 90 120" },
|
||||
// absolute moveto lineto
|
||||
{ "M10 20 L30,40", "M 10 20 L 30 40" },
|
||||
// relative moveto lineto
|
||||
{ "m10 20 l30,40", "M 10 20 L 40 60" },
|
||||
// relative moveto lineto lineto abs lineto
|
||||
{ "m10 20 30 40l30,40,50 60L200,300",
|
||||
"M 10 20 L 40 60 L 70 100 L 120 160 L 200 300" },
|
||||
// horizontal lineto
|
||||
{ "M10 20 H30", "M 10 20 L 30 20" },
|
||||
{ "M 10 20 H 30 40", "M 10 20 L 30 20 L 40 20" },
|
||||
{ "M10 20 H30,40-50", "M 10 20 L 30 20 L 40 20 L -50 20" },
|
||||
{ "m10 20 h30,40-50", "M 10 20 L 40 20 L 80 20 L 30 20" },
|
||||
// vertical lineto
|
||||
{ "M10 20 V30", "M 10 20 L 10 30" },
|
||||
{ "M10 20 V30 40", "M 10 20 L 10 30 L 10 40" },
|
||||
{ "M10 20 V30,40-50", "M 10 20 L 10 30 L 10 40 L 10 -50" },
|
||||
{ "m10 20 v30,40-50", "M 10 20 L 10 50 L 10 90 L 10 40" },
|
||||
// curveto
|
||||
{ "M10 20 C 30,40 50 60-70,80", "M 10 20 C 30 40, 50 60, -70 80" },
|
||||
{ "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 30 40, 50 60, -70 80 C 90 100, 110 120, 130 140" },
|
||||
{ "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
{ "m10 20 c 30,40 50 60-70,80 90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
// smooth curveto
|
||||
{ "M10 20 S 30,40-50,60", "M 10 20 C 10 20, 30 40, -50 60" },
|
||||
{ "M10 20 S 30,40 50 60-70,80,90 100",
|
||||
"M 10 20 C 10 20, 30 40, 50 60 C 70 80, -70 80, 90 100" },
|
||||
// quadratic curveto
|
||||
{ "M10 20 Q30 40 50 60", "M 10 20 Q 30 40, 50 60" },
|
||||
{ "M10 20 Q30 40 50 60,70,80-90 100",
|
||||
"M 10 20 Q 30 40, 50 60 Q 70 80, -90 100" },
|
||||
{ "m10 20 q 30,40 50 60-70,80 90 100",
|
||||
"M 10 20 Q 40 60, 60 80 Q -10 160, 150 180" },
|
||||
// smooth quadratic curveto
|
||||
{ "M10 20 T30 40", "M 10 20 Q 10 20, 30 40" },
|
||||
{ "M10 20 Q30 40 50 60 T70 80", "M 10 20 Q 30 40, 50 60 Q 70 80, 70 80" },
|
||||
{ "m10 20 q 30,40 50 60t-70,80",
|
||||
"M 10 20 Q 40 60, 60 80 Q 80 100, -10 160" },
|
||||
// elliptical arc. Exact numbers depend on too much math, so just verify
|
||||
// that these parse successfully
|
||||
{ "M 1 3 A 1 2 3 00 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 016 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 10,6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1,1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 16 7", "path" },
|
||||
// close path
|
||||
{ "M10 20 Z", "M 10 20 Z" },
|
||||
{ "m10 20 30 40 m 50 60 70 80 90 100z", "M 10 20 L 40 60 M 90 120 L 160 200 L 250 300 Z" },
|
||||
// must start with moveto
|
||||
{ " L10 20", NULL },
|
||||
// moveto args
|
||||
{ "M", NULL },
|
||||
{ "M,", NULL },
|
||||
{ "M10", NULL },
|
||||
{ "M10,", NULL },
|
||||
{ "M10x", NULL },
|
||||
{ "M10,x", NULL },
|
||||
{ "M10-20,", NULL },
|
||||
{ "M10-20-30", NULL },
|
||||
{ "M10-20-30 x", NULL },
|
||||
// closepath args
|
||||
{ "M10-20z10", NULL },
|
||||
{ "M10-20z,", NULL },
|
||||
// lineto args
|
||||
{ "M10-20L10", NULL },
|
||||
{ "M 10,10 L 20,20,30", NULL },
|
||||
{ "M 10,10 L 20,20,", NULL },
|
||||
// horizontal lineto args
|
||||
{ "M10-20H", NULL },
|
||||
{ "M10-20H,", NULL },
|
||||
{ "M10-20H30,", NULL },
|
||||
// vertical lineto args
|
||||
{ "M10-20v", NULL },
|
||||
{ "M10-20v,", NULL },
|
||||
{ "M10-20v30,", NULL },
|
||||
// curveto args
|
||||
{ "M10-20C1", NULL },
|
||||
{ "M10-20C1,", NULL },
|
||||
{ "M10-20C1 2", NULL },
|
||||
{ "M10-20C1,2,", NULL },
|
||||
{ "M10-20C1 2 3", NULL },
|
||||
{ "M10-20C1,2,3", NULL },
|
||||
{ "M10-20C1,2,3,", NULL },
|
||||
{ "M10-20C1 2 3 4", NULL },
|
||||
{ "M10-20C1,2,3,4", NULL },
|
||||
{ "M10-20C1,2,3,4,", NULL },
|
||||
{ "M10-20C1 2 3 4 5", NULL },
|
||||
{ "M10-20C1,2,3,4,5", NULL },
|
||||
{ "M10-20C1,2,3,4,5,", NULL },
|
||||
{ "M10-20C1,2,3,4,5,6,", NULL },
|
||||
// smooth curveto args
|
||||
{ "M10-20S1", NULL },
|
||||
{ "M10-20S1,", NULL },
|
||||
{ "M10-20S1 2", NULL },
|
||||
{ "M10-20S1,2,", NULL },
|
||||
{ "M10-20S1 2 3", NULL },
|
||||
{ "M10-20S1,2,3,", NULL },
|
||||
{ "M10-20S1,2,3,4,", NULL },
|
||||
// quadratic curveto args
|
||||
{ "M10-20Q1", NULL },
|
||||
{ "M10-20Q1,", NULL },
|
||||
{ "M10-20Q1 2", NULL },
|
||||
{ "M10-20Q1,2,", NULL },
|
||||
{ "M10-20Q1 2 3", NULL },
|
||||
{ "M10-20Q1,2,3", NULL },
|
||||
{ "M10-20Q1,2,3,", NULL },
|
||||
{ "M10 20 Q30 40 50 60,", NULL },
|
||||
// smooth quadratic curveto args
|
||||
{ "M10-20T1", NULL },
|
||||
{ "M10-20T1,", NULL },
|
||||
{ "M10 20 T 30 40,", NULL },
|
||||
// elliptical arc args
|
||||
{ "M10-20A1", NULL },
|
||||
{ "M10-20A1,", NULL },
|
||||
{ "M10-20A1 2", NULL },
|
||||
{ "M10-20A1 2,", NULL },
|
||||
{ "M10-20A1 2 3", NULL },
|
||||
{ "M10-20A1 2 3,", NULL },
|
||||
{ "M10-20A1 2 3 4", NULL },
|
||||
{ "M10-20A1 2 3 1", NULL },
|
||||
{ "M10-20A1 2 3,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 5", NULL },
|
||||
{ "M10-20A1 2 3 1 1", NULL },
|
||||
{ "M10-20A1 2 3,1,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 1 6", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,", NULL },
|
||||
{ "M 1 2 A 1 2 3 1.0 0.0 6 7", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,7,", NULL },
|
||||
// misc
|
||||
{ "M.. 1,0 0,100000", NULL },
|
||||
{ "M 10 20,M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, ", NULL },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskPath *path;
|
||||
char *string;
|
||||
char *string2;
|
||||
|
||||
if (g_test_verbose ())
|
||||
g_print ("%d: %s\n", i, tests[i].in);
|
||||
|
||||
path = gsk_path_parse (tests[i].in);
|
||||
if (tests[i].out)
|
||||
{
|
||||
g_assert_nonnull (path);
|
||||
string = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
if (strcmp (tests[i].out, "path") != 0)
|
||||
{
|
||||
/* Preferred, but doesn't work, because gsk_path_print()
|
||||
* prints numbers with insane accuracy
|
||||
*/
|
||||
/* g_assert_cmpstr (tests[i].out, ==, string); */
|
||||
path = gsk_path_parse (tests[i].out);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string2);
|
||||
}
|
||||
|
||||
path = gsk_path_parse (string);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string);
|
||||
g_free (string2);
|
||||
}
|
||||
else
|
||||
g_assert_null (path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test that circles and rectangles serialize as expected and can be
|
||||
* round-tripped through strings.
|
||||
*/
|
||||
static void
|
||||
test_serialize_custom_contours (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
GskPath *path1;
|
||||
char *string;
|
||||
char *string1;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (111, 222, 333, 444));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
string = gsk_path_to_string (path);
|
||||
g_assert_cmpstr ("M 150 100 A 50 50 0 0 0 50 100 A 50 50 0 0 0 150 100 z M 111 222 h 333 v 444 h -333 z", ==, string);
|
||||
|
||||
path1 = gsk_path_parse (string);
|
||||
string1 = gsk_path_to_string (path1);
|
||||
g_assert_cmpstr (string, ==, string1);
|
||||
|
||||
g_free (string);
|
||||
g_free (string1);
|
||||
gsk_path_unref (path);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/path/rsvg-parse", test_rsvg_parse);
|
||||
g_test_add_func ("/path/serialize-custom-contours", test_serialize_custom_contours);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
656
testsuite/gsk/path.c
Normal file
656
testsuite/gsk/path.c
Normal file
@@ -0,0 +1,656 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskpath.h"
|
||||
#include "gsk/gskpathbuilder.h"
|
||||
|
||||
static float
|
||||
random_weight (void)
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
return g_test_rand_double_range (1, 20);
|
||||
else
|
||||
return 1.0 / g_test_rand_double_range (1, 20);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_degenerate_path (guint max_contours)
|
||||
{
|
||||
#define N_DEGENERATE_PATHS 15
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
|
||||
{
|
||||
case 0:
|
||||
/* empty path */
|
||||
break;
|
||||
|
||||
case 1:
|
||||
/* a single point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
/* N points */
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
/* 1 closed point */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
/* the same point closed N times */
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
for (i = 0; i < MIN (10, max_contours); i++)
|
||||
{
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
/* a zero-width and zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0, 0));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
/* a zero-width rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0,
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 7:
|
||||
/* a zero-height rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
0));
|
||||
break;
|
||||
|
||||
case 8:
|
||||
/* a negative-size rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 0),
|
||||
g_test_rand_double_range (-1000, 0)));
|
||||
break;
|
||||
|
||||
case 9:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 10:
|
||||
/* an absolutely random rect */
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)));
|
||||
break;
|
||||
|
||||
case 11:
|
||||
/* an absolutely random circle */
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 12:
|
||||
/* a zero-length line */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_line_to (builder, point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
/* a curve with start == end */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
point.x, point.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case 14:
|
||||
/* a conic with start == end */
|
||||
{
|
||||
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||
gsk_path_builder_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
point.x, point.y,
|
||||
random_weight ());
|
||||
}
|
||||
break;
|
||||
|
||||
case N_DEGENERATE_PATHS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours);
|
||||
|
||||
static void
|
||||
add_shape_contour (GskPathBuilder *builder)
|
||||
{
|
||||
#define N_SHAPE_CONTOURS 3
|
||||
switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_add_rect (builder,
|
||||
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (1, 1000),
|
||||
g_test_rand_double_range (1, 1000)));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_add_circle (builder,
|
||||
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000)),
|
||||
g_test_rand_double_range (1, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
GskPath *path = create_random_path (1);
|
||||
gsk_path_builder_add_path (builder, path);
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
break;
|
||||
|
||||
case N_SHAPE_CONTOURS:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_standard_contour (GskPathBuilder *builder)
|
||||
{
|
||||
guint i, n;
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
{
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
else
|
||||
gsk_path_builder_rel_move_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
}
|
||||
|
||||
/* that 20 is random, but should be enough to get some
|
||||
* crazy self-intersecting shapes */
|
||||
n = g_test_rand_int_range (1, 20);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
switch (g_test_rand_int_range (0, 8))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 1:
|
||||
gsk_path_builder_rel_line_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
gsk_path_builder_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
gsk_path_builder_rel_quad_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
gsk_path_builder_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
gsk_path_builder_rel_cubic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
gsk_path_builder_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
random_weight ());
|
||||
break;
|
||||
|
||||
case 7:
|
||||
gsk_path_builder_rel_conic_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
random_weight ());
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_test_rand_bit ())
|
||||
gsk_path_builder_close (builder);
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_random_path (guint max_contours)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
guint i, n;
|
||||
|
||||
/* 5% chance for a weird shape */
|
||||
if (!g_test_rand_int_range (0, 20))
|
||||
return create_random_degenerate_path (max_contours);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
n = g_test_rand_int_range (1, 10);
|
||||
n = MIN (n, max_contours);
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
/* 2/3 of shapes are standard contours */
|
||||
if (g_test_rand_int_range (0, 3))
|
||||
add_standard_contour (builder);
|
||||
else
|
||||
add_shape_contour (builder);
|
||||
}
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GskPathOperation op;
|
||||
graphene_point_t pts[4];
|
||||
float weight;
|
||||
} PathOperation;
|
||||
|
||||
static void
|
||||
_g_string_append_double (GString *string,
|
||||
double d)
|
||||
{
|
||||
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
||||
|
||||
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d);
|
||||
g_string_append (string, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
_g_string_append_point (GString *string,
|
||||
const graphene_point_t *pt)
|
||||
{
|
||||
_g_string_append_double (string, pt->x);
|
||||
g_string_append_c (string, ' ');
|
||||
_g_string_append_double (string, pt->y);
|
||||
}
|
||||
|
||||
static void
|
||||
path_operation_print (const PathOperation *p,
|
||||
GString *string)
|
||||
{
|
||||
switch (p->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
g_string_append (string, "M ");
|
||||
_g_string_append_point (string, &p->pts[0]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
g_string_append (string, " Z");
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
g_string_append (string, " L ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
g_string_append (string, " Q ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
g_string_append (string, " C ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[3]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
/* This is not valid SVG */
|
||||
g_string_append (string, " O ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_double (string, p->weight);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
path_operation_equal (const PathOperation *p1,
|
||||
const PathOperation *p2,
|
||||
float epsilon)
|
||||
{
|
||||
if (p1->op != p2->op)
|
||||
return FALSE;
|
||||
|
||||
/* No need to compare pts[0] for most ops, that's just
|
||||
* duplicate work. */
|
||||
switch (p1->op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
return graphene_point_near (&p1->pts[0], &p2->pts[0], epsilon);
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
case GSK_PATH_CLOSE:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon);
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon);
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
|
||||
&& graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon);
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
|
||||
&& G_APPROX_VALUE (p1->weight, p2->weight, epsilon);
|
||||
|
||||
default:
|
||||
g_return_val_if_reached (FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_path_operation_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_array_append_vals (user_data,
|
||||
(PathOperation[1]) { {
|
||||
op,
|
||||
{
|
||||
GRAPHENE_POINT_INIT(pts[0].x, pts[0].y),
|
||||
GRAPHENE_POINT_INIT(n_pts > 1 ? pts[1].x : 0,
|
||||
n_pts > 1 ? pts[1].y : 0),
|
||||
GRAPHENE_POINT_INIT(n_pts > 2 ? pts[2].x : 0,
|
||||
n_pts > 2 ? pts[2].y : 0),
|
||||
GRAPHENE_POINT_INIT(n_pts > 3 ? pts[3].x : 0,
|
||||
n_pts > 3 ? pts[3].y : 0)
|
||||
},
|
||||
weight
|
||||
} },
|
||||
1);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GArray *
|
||||
collect_path (GskPath *path)
|
||||
{
|
||||
GArray *array = g_array_new (FALSE, FALSE, sizeof (PathOperation));
|
||||
|
||||
/* Use -1 here because we want all the flags, even future additions */
|
||||
gsk_path_foreach (path, -1, collect_path_operation_cb, array);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
static void
|
||||
assert_path_equal_func (const char *domain,
|
||||
const char *file,
|
||||
int line,
|
||||
const char *func,
|
||||
GskPath *path1,
|
||||
GskPath *path2,
|
||||
float epsilon)
|
||||
{
|
||||
GArray *ops1, *ops2;
|
||||
guint i;
|
||||
|
||||
ops1 = collect_path (path1);
|
||||
ops2 = collect_path (path2);
|
||||
|
||||
for (i = 0; i < MAX (ops1->len, ops2->len); i++)
|
||||
{
|
||||
PathOperation *op1 = i < ops1->len ? &g_array_index (ops1, PathOperation, i) : NULL;
|
||||
PathOperation *op2 = i < ops2->len ? &g_array_index (ops2, PathOperation, i) : NULL;
|
||||
|
||||
if (op1 == NULL || op2 == NULL || !path_operation_equal (op1, op2, epsilon))
|
||||
{
|
||||
GString *string;
|
||||
guint j;
|
||||
|
||||
/* Find the operation we start to print */
|
||||
for (j = i; j-- > 0; )
|
||||
{
|
||||
PathOperation *op = &g_array_index (ops1, PathOperation, j);
|
||||
if (op->op == GSK_PATH_MOVE)
|
||||
break;
|
||||
if (j + 3 == i)
|
||||
{
|
||||
j = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string = g_string_new (j == 0 ? "" : "... ");
|
||||
for (; j < i; j++)
|
||||
{
|
||||
PathOperation *op = &g_array_index (ops1, PathOperation, j);
|
||||
path_operation_print (op, string);
|
||||
g_string_append_c (string, ' ');
|
||||
}
|
||||
|
||||
g_string_append (string, "\\\n ");
|
||||
if (op1)
|
||||
{
|
||||
path_operation_print (op1, string);
|
||||
if (ops1->len > i + 1)
|
||||
g_string_append (string, " ...");
|
||||
}
|
||||
g_string_append (string, "\n ");
|
||||
if (op1)
|
||||
{
|
||||
path_operation_print (op2, string);
|
||||
if (ops2->len > i + 1)
|
||||
g_string_append (string, " ...");
|
||||
}
|
||||
|
||||
g_assertion_message (domain, file, line, func, string->str);
|
||||
|
||||
g_string_free (string, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
g_array_free (ops1, TRUE);
|
||||
g_array_free (ops2, TRUE);
|
||||
}
|
||||
#define assert_path_equal(p1,p2) assert_path_equal_func(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (p1),(p2), FLOAT_EPSILON)
|
||||
#define assert_path_equal_with_epsilon(p1,p2, epsilon) \
|
||||
assert_path_equal_func(G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, (p1),(p2), (epsilon))
|
||||
|
||||
static void
|
||||
test_create (void)
|
||||
{
|
||||
GskPath *path1, *path2, *built;
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
char *s;
|
||||
GString *str;
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
builder = gsk_path_builder_new ();
|
||||
path1 = create_random_path (G_MAXUINT);
|
||||
gsk_path_builder_add_path (builder, path1);
|
||||
path2 = create_random_path (G_MAXUINT);
|
||||
gsk_path_builder_add_path (builder, path2);
|
||||
built = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
str = g_string_new (NULL);
|
||||
gsk_path_print (path1, str);
|
||||
if (!gsk_path_is_empty (path1) && !gsk_path_is_empty (path2))
|
||||
g_string_append_c (str, ' ');
|
||||
gsk_path_print (path2, str);
|
||||
|
||||
s = gsk_path_to_string (built);
|
||||
|
||||
g_assert_cmpstr (s, ==, str->str);
|
||||
g_string_free (str, TRUE);
|
||||
g_free (s);
|
||||
|
||||
gsk_path_unref (built);
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_parse (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 1000; i++)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
char *string1, *string2;
|
||||
|
||||
path1 = create_random_path (G_MAXUINT);
|
||||
string1 = gsk_path_to_string (path1);
|
||||
g_assert_nonnull (string1);
|
||||
|
||||
path2 = gsk_path_parse (string1);
|
||||
g_assert_nonnull (path2);
|
||||
string2 = gsk_path_to_string (path2);
|
||||
g_assert_nonnull (string2);
|
||||
|
||||
assert_path_equal_with_epsilon (path1, path2, 1.f / 1024);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
gsk_path_unref (path1);
|
||||
g_free (string2);
|
||||
g_free (string1);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
gtk_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/path/create", test_create);
|
||||
g_test_add_func ("/path/parse", test_parse);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
Reference in New Issue
Block a user