Files
gtk/gsk/gl/gskglglyphylibrary.c
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

545 lines
16 KiB
C

/* 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;
}