Compare commits

...

24 Commits

Author SHA1 Message Date
Matthias Clasen
e2c7936ceb Store synthetic font params in the text node
This lets use reduce the overhead by doing the
fontconfig calls only once, instead of every
time.
2023-09-26 21:59:57 -04:00
Matthias Clasen
6846a5aaa7 movingtext: Avoid fontconfig overhead
Instead of redoing the pango layout every
frame, just scale the node.
2023-09-24 18:28:00 -04:00
Matthias Clasen
1cff54ea92 gsk: Make glyphy optional for now
Enable glyphy by default, but make it possible
to get the previous cairo rendering by setting
GSK_DEBUG=no-glyphy in the environment.

Keeping this optional will make it easier to
debug issues and compare output.
2023-09-24 17:47:21 -04:00
Matthias Clasen
72a704d145 Add an animated text example 2023-09-24 17:47:21 -04:00
Matthias Clasen
e2969bec04 Add a font rendering test 2023-09-24 17:47:21 -04:00
Matthias Clasen
370c47e595 glyphy: Implement synthetic italic
Use the font matrix found in the FcPattern to
transform the quads we use to render the glyphs.

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

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

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

Otherwise, we can dispatch to a new function which will
eventually use Glyphy to shape to SDF content and upload
to an alternate texture atlas.
2023-09-24 17:47:21 -04:00
Matthias Clasen
3195a1e6df build: Add a dependency on glyphy
We have a subproject, and we link statically
if we can, to avoid depending on a project
that is not generally packaged in distros.
2023-09-24 17:47:21 -04:00
Matthias Clasen
b21a5d9a06 Add some tests for pathops 2023-09-24 17:47:21 -04:00
Matthias Clasen
01d844a994 Implement boolean operations on paths
Implement union, intersection, difference and
symmetric difference of two paths, as well as
simplification of a single path.
2023-09-24 17:47:21 -04:00
Matthias Clasen
b41c45ab5b Add tests for gsk_curve_intersect 2023-09-24 17:47:21 -04:00
Matthias Clasen
6f97c02c4d curve: Add gsk_curve_intersect
Add a way to find the intersections of two curves.
We can handle some curve-line intersections directly,
the general case is handled via bisection.
2023-09-24 17:47:21 -04:00
Matthias Clasen
fffa490280 curve: Some refactoring
Move cusp-related code to gskcurveintersect.c.
Add functions to find cusps and inflection points of cubics.
These will be used for intersections and in the stroker.
2023-09-24 17:47:21 -04:00
33 changed files with 5294 additions and 189 deletions

View File

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

View File

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

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

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

View File

@@ -0,0 +1,142 @@
/* gskglglyphylibraryprivate.h
*
* Copyright 2020-2022 Christian Hergert <chergert@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
#define __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__
#include <glyphy.h>
#include <pango/pango.h>
#include "gskgltexturelibraryprivate.h"
G_BEGIN_DECLS
#define GSK_TYPE_GL_GLYPHY_LIBRARY (gsk_gl_glyphy_library_get_type())
typedef guint FontKey;
extern GQuark quark_glyphy_font_key;
static inline FontKey
gsk_gl_glyphy_library_get_font_key (PangoFont *font)
{
FontKey key;
key = (FontKey) GPOINTER_TO_UINT (g_object_get_qdata ((GObject *)font, quark_glyphy_font_key));
if (key == 0)
{
PangoFontDescription *desc = pango_font_describe (font);
pango_font_description_set_size (desc, 10 * PANGO_SCALE);
key = (FontKey) pango_font_description_hash (desc);
pango_font_description_free (desc);
g_object_set_qdata ((GObject *)font, quark_glyphy_font_key, GUINT_TO_POINTER (key));
}
return key;
}
static inline float
gsk_gl_glyphy_library_get_font_scale (PangoFont *font)
{
hb_font_t *hbfont;
int x_scale, y_scale;
hbfont = pango_font_get_hb_font (font);
hb_font_get_scale (hbfont, &x_scale, &y_scale);
return MAX (x_scale, y_scale) / 1000.0;
}
typedef struct _GskGLGlyphyKey
{
FontKey font;
PangoGlyph glyph;
} GskGLGlyphyKey;
typedef struct _GskGLGlyphyValue
{
GskGLTextureAtlasEntry entry;
struct {
float min_x;
float min_y;
float max_x;
float max_y;
} extents;
guint nominal_w;
guint nominal_h;
guint atlas_x;
guint atlas_y;
} GskGLGlyphyValue;
G_DECLARE_FINAL_TYPE (GskGLGlyphyLibrary, gsk_gl_glyphy_library, GSK, GL_GLYPHY_LIBRARY, GskGLTextureLibrary)
struct _GskGLGlyphyLibrary
{
GskGLTextureLibrary parent_instance;
glyphy_arc_accumulator_t *acc;
GArray *acc_endpoints;
guint item_w;
guint item_h_q;
struct {
GskGLGlyphyKey key;
const GskGLGlyphyValue *value;
} front[256];
};
GskGLGlyphyLibrary *gsk_gl_glyphy_library_new (GskGLDriver *driver);
gboolean gsk_gl_glyphy_library_add (GskGLGlyphyLibrary *self,
GskGLGlyphyKey *key,
PangoFont *font,
const GskGLGlyphyValue **out_value);
static inline guint
gsk_gl_glyphy_library_lookup_or_add (GskGLGlyphyLibrary *self,
const GskGLGlyphyKey *key,
PangoFont *font,
const GskGLGlyphyValue **out_value)
{
GskGLTextureAtlasEntry *entry;
guint front_index = key->glyph & 0xFF;
if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
{
*out_value = self->front[front_index].value;
}
else if (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry))
{
*out_value = (GskGLGlyphyValue *)entry;
self->front[front_index].key = *key;
self->front[front_index].value = *out_value;
}
else
{
GskGLGlyphyKey *k = g_slice_copy (sizeof *key, key);
gsk_gl_glyphy_library_add (self, k, font, out_value);
self->front[front_index].key = *key;
self->front[front_index].value = *out_value;
}
return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value);
}
G_END_DECLS
#endif /* __GSK_GL_GLYPHY_LIBRARY_PRIVATE_H__ */

View File

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

View File

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

View File

@@ -39,6 +39,7 @@
#include "gskglcommandqueueprivate.h"
#include "gskgldriverprivate.h"
#include "gskglglyphlibraryprivate.h"
#include "gskglglyphylibraryprivate.h"
#include "gskgliconlibraryprivate.h"
#include "gskglprogramprivate.h"
#include "gskglrenderjobprivate.h"
@@ -152,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.
*/
@@ -2953,10 +2957,10 @@ compute_phase_and_pos (float value, float *pos)
}
static inline void
gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
gsk_gl_render_job_visit_text_node_legacy (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
{
const PangoFont *font = gsk_text_node_get_font (node);
const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
@@ -3093,6 +3097,234 @@ 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 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;
const PangoMatrix *matrix;
#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);
embolden = gsk_text_node_get_font_embolden (node);
matrix = gsk_text_node_get_font_matrix (node);
glyphs = gsk_text_node_get_glyphs (node, NULL);
library = job->driver->glyphy_library;
offset = gsk_text_node_get_offset (node);
x = offset->x + job->offset_x;
y = offset->y + job->offset_y;
rgba_to_half (color, cc);
c = cc;
gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, glyphy));
batch = gsk_gl_command_queue_get_batch (job->command_queue);
vertices = gsk_gl_command_queue_add_n_vertices (job->command_queue, num_glyphs);
lookup.font = gsk_gl_glyphy_library_get_font_key (font);
font_scale = gsk_gl_glyphy_library_get_font_scale (font);
for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++)
{
const GskGLGlyphyValue *glyph;
float cx = 0, cy = 0;
guint texture_id;
lookup.glyph = gi->glyph;
texture_id = gsk_gl_glyphy_library_lookup_or_add (library, &lookup, font, &glyph);
if G_UNLIKELY (texture_id == 0)
continue;
if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff)
{
if G_LIKELY (last_texture != 0)
{
guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count;
/* Since we have batched added our VBO vertices to avoid repeated
* calls to the buffer, we need to manually tweak the vbo offset
* of the new batch as otherwise it will point at the end of our
* vbo array.
*/
gsk_gl_render_job_split_draw (job);
batch = gsk_gl_command_queue_get_batch (job->command_queue);
batch->draw.vbo_offset = vbo_offset;
}
gsk_gl_program_set_uniform4i (job->current_program,
UNIFORM_GLYPHY_ATLAS_INFO, 0,
GSK_GL_TEXTURE_LIBRARY (library)->atlas_width,
GSK_GL_TEXTURE_LIBRARY (library)->atlas_height,
library->item_w,
library->item_h_q);
gsk_gl_program_set_uniform_texture (job->current_program,
UNIFORM_SHARED_SOURCE, 0,
GL_TEXTURE_2D,
GL_TEXTURE0,
texture_id);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_GAMMA_ADJUST, 0,
1.0);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_CONTRAST, 0,
1.0);
/* 0.0208 is the value used by freetype for synthetic emboldening */
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_BOLDNESS, 0,
embolden ? 0.0208 * GRID_SIZE : 0.0);
#if 0
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_OUTLINE_THICKNESS, 0,
1.0);
gsk_gl_program_set_uniform1f (job->current_program,
UNIFORM_GLYPHY_OUTLINE, 0,
1.0);
#endif
last_texture = texture_id;
}
cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
if G_UNLIKELY (gi->geometry.y_offset != 0)
cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
x_position += gi->geometry.width;
EncodedGlyph encoded[4];
#define ENCODE_CORNER(_cx, _cy) \
G_STMT_START { \
float _dx = _cx * (glyph->extents.max_x - glyph->extents.min_x); \
float _dy = _cy * (glyph->extents.max_y - glyph->extents.min_y); \
float _vx = x + cx + font_scale * (glyph->extents.min_x + matrix->xx * _dx + matrix->xy * _dy); \
float _vy = y + cy - font_scale * (glyph->extents.min_y + matrix->yx * _dx + matrix->yy * _dy); \
encoded_glyph_init (&encoded[_cx * 2 + _cy], _vx, _vy, _cx, _cy, glyph); \
} G_STMT_END
ENCODE_CORNER (0, 0);
ENCODE_CORNER (0, 1);
ENCODE_CORNER (1, 0);
ENCODE_CORNER (1, 1);
#undef ENCODE_CORNER
add_encoded_glyph (vertices++, &encoded[0], c);
add_encoded_glyph (vertices++, &encoded[1], c);
add_encoded_glyph (vertices++, &encoded[2], c);
add_encoded_glyph (vertices++, &encoded[1], c);
add_encoded_glyph (vertices++, &encoded[2], c);
add_encoded_glyph (vertices++, &encoded[3], c);
batch->draw.vbo_count += GSK_GL_N_VERTICES;
used++;
}
if (used != num_glyphs)
gsk_gl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used);
gsk_gl_render_job_end_draw (job);
}
static inline void
gsk_gl_render_job_visit_text_node (GskGLRenderJob *job,
const GskRenderNode *node,
const GdkRGBA *color,
gboolean force_color)
{
if (job->use_glyphy && !gsk_text_node_has_color_glyphs (node))
gsk_gl_render_job_visit_text_node_glyphy (job, node, color);
else
gsk_gl_render_job_visit_text_node_legacy (job, node, color, force_color);
}
static inline void
gsk_gl_render_job_visit_shadow_node (GskGLRenderJob *job,
const GskRenderNode *node)
@@ -4476,6 +4708,15 @@ gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
job->debug_fallback = !!debug_fallback;
}
void
gsk_gl_render_job_set_use_glyphy (GskGLRenderJob *job,
gboolean use_glyphy)
{
g_return_if_fail (job != NULL);
job->use_glyphy = !!use_glyphy;
}
static int
get_framebuffer_format (GdkGLContext *context,
guint framebuffer)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1115
gsk/gskcurveintersect.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@
#include "gskpathopprivate.h"
#include "gskpath.h"
#include "gskpathprivate.h"
#include "gskboundingboxprivate.h"
G_BEGIN_DECLS
@@ -187,6 +188,19 @@ int gsk_curve_get_curvature_points (const GskCurve
int gsk_curve_get_cusps (const GskCurve *curve,
float t[2]);
int gsk_curve_intersect (const GskCurve *curve1,
const GskCurve *curve2,
float *t1,
float *t2,
graphene_point_t *p,
GskPathIntersection *kind,
int n);
int gsk_curve_self_intersect (const GskCurve *curve,
float *t1,
graphene_point_t *p,
int n);
G_END_DECLS

View File

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

View File

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

1712
gsk/gskpathops.c Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -43,6 +43,10 @@
#endif
#include <hb-ot.h>
#ifdef HAVE_PANGOFT
#include <pango/pangofc-font.h>
#endif
/* maximal number of rectangles we keep in a diff region before we throw
* the towel and just use the bounding box of the parent node.
* Meant to avoid performance corner cases.
@@ -5460,6 +5464,8 @@ struct _GskTextNode
PangoFont *font;
gboolean has_color_glyphs;
gboolean embolden;
PangoMatrix matrix;
GdkRGBA color;
graphene_point_t offset;
@@ -5468,6 +5474,37 @@ struct _GskTextNode
PangoGlyphInfo *glyphs;
};
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 void
gsk_text_node_finalize (GskRenderNode *node)
{
@@ -5593,6 +5630,8 @@ gsk_text_node_new (PangoFont *font,
self->offset = *offset;
self->has_color_glyphs = FALSE;
get_synthetic_font_params (self->font, &self->embolden, &self->matrix);
glyph_infos = g_malloc_n (glyphs->num_glyphs, sizeof (PangoGlyphInfo));
n = 0;
@@ -5725,6 +5764,22 @@ gsk_text_node_get_offset (const GskRenderNode *node)
return &self->offset;
}
gboolean
gsk_text_node_get_font_embolden (const GskRenderNode *node)
{
const GskTextNode *self = (const GskTextNode *) node;
return self->embolden;
}
const PangoMatrix *
gsk_text_node_get_font_matrix (const GskRenderNode *node)
{
const GskTextNode *self = (const GskTextNode *) node;
return &self->matrix;
}
/* }}} */
/* {{{ GSK_BLUR_NODE */

View File

@@ -87,6 +87,11 @@ gboolean gsk_container_node_is_disjoint (const GskRenderNode
gboolean gsk_render_node_use_offscreen_for_opacity (const GskRenderNode *node);
gboolean gsk_text_node_get_font_embolden (const GskRenderNode *node);
const PangoMatrix *
gsk_text_node_get_font_matrix (const GskRenderNode *node);
G_END_DECLS

View File

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

View File

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

View File

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

6
subprojects/glyphy.wrap Normal file
View File

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

123
tests/bigfont.c Normal file
View File

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

View File

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

211
tests/movingtext.c Normal file
View File

@@ -0,0 +1,211 @@
#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;
PangoLayout *layout;
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);
int width, height;
int pwidth, pheight;
GdkRGBA color;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
gtk_widget_get_color (widget, &color);
pango_layout_get_pixel_size (self->layout, &pwidth, &pheight);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0.5 * width, 0.5 * height));
gtk_snapshot_scale (snapshot, self->size / 150.f, self->size / 150.f);
gtk_snapshot_rotate (snapshot, self->angle);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (- 0.5 * pwidth, - 0.5 * pheight));
gtk_snapshot_append_layout (snapshot, self->layout, &color);
gtk_snapshot_restore (snapshot);
}
static void
demo_widget_dispose (GObject *object)
{
DemoWidget *self = DEMO_WIDGET (object);
g_free (self->text);
g_clear_object (&self->layout);
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;
PangoFontDescription *desc;
demo = g_object_new (DEMO_TYPE_WIDGET, NULL);
demo->text = g_strndup (text, length);
demo->layout = gtk_widget_create_pango_layout (GTK_WIDGET (demo), demo->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, 150 * PANGO_SCALE);
pango_layout_set_font_description (demo->layout, desc);
pango_font_description_free (desc);
return GTK_WIDGET (demo);
}
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *demo;
char *text = NULL;
gsize length;
gtk_init ();
window = gtk_window_new ();
if (argc > 1)
{
GError *error = NULL;
if (!g_file_get_contents (argv[1], &text, &length, &error))
{
g_warning ("%s", error->message);
g_error_free (error);
text = NULL;
}
}
if (!text)
{
text = g_strdup ("Best Aa");
length = strlen (text);
}
demo = demo_widget_new (text, length);
gtk_window_set_child (GTK_WINDOW (window), demo);
g_free (text);
gtk_window_present (GTK_WINDOW (window));
gtk_widget_grab_focus (demo);
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
return 0;
}

View File

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

View File

@@ -406,7 +406,9 @@ endforeach
internal_tests = [
[ 'curve' ],
[ 'curve-special-cases' ],
[ 'curve-intersect' ],
[ 'path-private' ],
['path-ops', [ 'path-utils.c' ] ],
[ 'diff' ],
[ 'half-float' ],
['rounded-rect'],

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

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