Merge branch 'subpixel-positions-revisited' into 'master'

Improve subpixel positioning

See merge request cairo/cairo!617
This commit is contained in:
Matthias Clasen 2026-02-09 15:13:03 +00:00
commit 5a6e37ef19
9 changed files with 178 additions and 32 deletions

View file

@ -60,6 +60,7 @@ static const cairo_font_options_t _cairo_font_options_nil = {
CAIRO_COLOR_MODE_DEFAULT,
CAIRO_COLOR_PALETTE_DEFAULT,
NULL, 0, /* custom palette */
CAIRO_SUBPIXEL_POSITIONS_DEFAULT,
};
/**
@ -82,6 +83,7 @@ _cairo_font_options_init_default (cairo_font_options_t *options)
options->palette_index = CAIRO_COLOR_PALETTE_DEFAULT;
options->custom_palette = NULL;
options->custom_palette_size = 0;
options->subpixel_positions = CAIRO_SUBPIXEL_POSITIONS_DEFAULT;
}
void
@ -103,6 +105,7 @@ _cairo_font_options_init_copy (cairo_font_options_t *options,
options->custom_palette = (cairo_palette_color_t *) malloc (sizeof (cairo_palette_color_t) * options->custom_palette_size);
memcpy (options->custom_palette, other->custom_palette, sizeof (cairo_palette_color_t) * options->custom_palette_size);
}
options->subpixel_positions = other->subpixel_positions;
}
cairo_bool_t
@ -117,7 +120,8 @@ _cairo_font_options_compare (const cairo_font_options_t *a,
a->round_glyph_positions != b->round_glyph_positions ||
a->color_mode != b->color_mode ||
a->palette_index != b->palette_index ||
a->custom_palette_size != b->custom_palette_size)
a->custom_palette_size != b->custom_palette_size ||
a->subpixel_positions != b->subpixel_positions)
{
return FALSE;
}
@ -287,6 +291,8 @@ cairo_font_options_merge (cairo_font_options_t *options,
options->hint_metrics = other->hint_metrics;
if (other->round_glyph_positions != CAIRO_ROUND_GLYPH_POS_DEFAULT)
options->round_glyph_positions = other->round_glyph_positions;
if (other->subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_DEFAULT)
options->subpixel_positions = other->subpixel_positions;
if (other->variations) {
if (options->variations) {
@ -349,6 +355,7 @@ cairo_font_options_equal (const cairo_font_options_t *options,
options->hint_style == other->hint_style &&
options->hint_metrics == other->hint_metrics &&
options->round_glyph_positions == other->round_glyph_positions &&
options->subpixel_positions == other->subpixel_positions &&
((options->variations == NULL && other->variations == NULL) ||
(options->variations != NULL && other->variations != NULL &&
strcmp (options->variations, other->variations) == 0)) &&
@ -393,7 +400,8 @@ cairo_font_options_hash (const cairo_font_options_t *options)
(options->lcd_filter << 8) |
(options->hint_style << 12) |
(options->hint_metrics << 16) |
(options->color_mode << 20)) ^ hash;
(options->subpixel_positions << 20) |
(options->color_mode << 22)) ^ hash;
}
/**
@ -869,3 +877,43 @@ cairo_font_options_get_custom_palette_color (cairo_font_options_t *options,
return CAIRO_STATUS_INVALID_INDEX;
}
/**
* cairo_font_options_set_subpixel_positions:
* @options: a #cairo_font_options_t
* @subpixel_positions: the new value
*
* Sets the subpixel positions value for the font options object.
* This controls whether glyph positions are rounded when rendering.
*
* Since: 1.20
**/
void
cairo_font_options_set_subpixel_positions (cairo_font_options_t *options,
cairo_subpixel_positions_t subpixel_positions)
{
if (cairo_font_options_status (options))
return;
options->subpixel_positions = subpixel_positions;
}
/**
* cairo_font_options_get_subpixel_positions:
* @options: a #cairo_font_options_t
*
* Gets the subpixel positions value for the font options object.
* See the documentation for #cairo_subpixel_positions_t for full details.
*
* Return value: the subpixel positions value for the font options object
*
* Since: 1.20
*/
cairo_public cairo_subpixel_positions_t
cairo_font_options_get_subpixel_positions (const cairo_font_options_t *options)
{
if (cairo_font_options_status ((cairo_font_options_t *) options))
return CAIRO_SUBPIXEL_POSITIONS_DEFAULT;
return options->subpixel_positions;
}

View file

@ -2449,10 +2449,11 @@ _cairo_ft_scaled_glyph_load_glyph (cairo_ft_scaled_font_t *scaled_font,
_cairo_ft_scaled_glyph_vertical_layout_bearing_fix (scaled_font, face->glyph);
if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
unsigned int bits = _cairo_subpixel_bits (scaled_font->base.options.subpixel_positions);
FT_Pos xshift, yshift;
xshift = _cairo_scaled_glyph_xphase (scaled_glyph) << 4;
yshift = _cairo_scaled_glyph_yphase (scaled_glyph) << 4;
xshift = _cairo_scaled_glyph_xphase (scaled_glyph) << (6 - bits);
yshift = _cairo_scaled_glyph_yphase (scaled_glyph) << (6 - bits);
FT_Outline_Translate (&face->glyph->outline, xshift, -yshift);
}

View file

@ -845,9 +845,6 @@ _cairo_image_scaled_glyph_fini (cairo_scaled_font_t *scaled_font,
CAIRO_MUTEX_UNLOCK (_cairo_glyph_cache_mutex);
}
#define PHASE(x) ((int)(floor (4 * (x + 0.125)) - 4 * floor (x + 0.125)))
#define POSITION(x) ((int) floor (x + 0.125))
static cairo_int_status_t
composite_glyphs (void *_dst,
cairo_operator_t op,
@ -889,12 +886,14 @@ composite_glyphs (void *_dst,
for (i = 0; i < info->num_glyphs; i++) {
unsigned long index = info->glyphs[i].index;
const void *glyph;
unsigned long xphase, yphase;
xphase = PHASE(info->glyphs[i].x);
yphase = PHASE(info->glyphs[i].y);
index = index | (xphase << 24) | (yphase << 26);
if (info->font->options.subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_OFF) {
int res = _cairo_subpixel_resolution (info->font->options.subpixel_positions);
unsigned long xphase, yphase;
xphase = _cairo_subpixel_phase (info->glyphs[i].x, res);
yphase = _cairo_subpixel_phase (info->glyphs[i].y, res);
index = index | (xphase << XSHIFT) | (yphase << YSHIFT);
}
glyph = pixman_glyph_cache_lookup (glyph_cache, info->font, (void *)(uintptr_t)index);
if (!glyph) {
@ -925,8 +924,15 @@ composite_glyphs (void *_dst,
}
}
pg->x = POSITION (info->glyphs[i].x);
pg->y = POSITION (info->glyphs[i].y);
if (info->font->options.subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_OFF) {
int res = _cairo_subpixel_resolution (info->font->options.subpixel_positions);
pg->x = _cairo_subpixel_position (info->glyphs[i].x, res);
pg->y = _cairo_subpixel_position (info->glyphs[i].y, res);
}
else {
pg->x = _cairo_lround (info->glyphs[i].x);
pg->y = _cairo_lround (info->glyphs[i].y);
}
pg->glyph = glyph;
pg++;
}
@ -954,6 +960,20 @@ composite_glyphs (void *_dst,
glyph_cache, pg - pglyphs, pglyphs);
}
/* Don't keep 64x64 versions of each glyph in the cache */
if (info->font->options.subpixel_positions == CAIRO_SUBPIXEL_POSITIONS_FINE) {
int res = _cairo_subpixel_resolution (info->font->options.subpixel_positions);
for (i = 0; i < info->num_glyphs; i++) {
unsigned long index = info->glyphs[i].index;
unsigned long xphase, yphase;
xphase = _cairo_subpixel_phase (info->glyphs[i].x, res);
yphase = _cairo_subpixel_phase (info->glyphs[i].y, res);
index = index | (xphase << XSHIFT) | (yphase << YSHIFT);
pixman_glyph_cache_remove (glyph_cache, info->font, (void *)(uintptr_t)index);
}
}
out_thaw:
pixman_glyph_cache_thaw (glyph_cache);

View file

@ -204,6 +204,7 @@ struct _cairo_font_options {
unsigned int palette_index;
cairo_palette_color_t *custom_palette;
unsigned int custom_palette_size;
cairo_subpixel_positions_t subpixel_positions;
};
struct _cairo_glyph_text_info {

View file

@ -207,8 +207,11 @@ _cairo_user_scaled_glyph_init_record_glyph (cairo_user_scaled_font_t *scaled_fon
if (recording_surface)
cairo_surface_destroy (recording_surface);
recording_surface = _cairo_user_scaled_font_create_recording_surface (scaled_font, FALSE, foreground_color);
recording_surface->device_transform.x0 = .25 * _cairo_scaled_glyph_xphase (scaled_glyph);
recording_surface->device_transform.y0 = .25 * _cairo_scaled_glyph_yphase (scaled_glyph);
if (scaled_font->base.options.subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_OFF) {
double res = _cairo_subpixel_resolution (scaled_font->base.options.subpixel_positions);
recording_surface->device_transform.x0 = _cairo_scaled_glyph_xphase (scaled_glyph) / res;
recording_surface->device_transform.y0 = _cairo_scaled_glyph_yphase (scaled_glyph) / res;
}
cr = _cairo_user_scaled_font_create_recording_context (scaled_font, recording_surface, FALSE);

View file

@ -1570,9 +1570,6 @@ check_composite_glyphs (const cairo_composite_rectangles_t *extents,
* enough room for padding */
#define _cairo_sz_xGlyphElt (sz_xGlyphElt + 4)
#define PHASE(x) ((int)(floor (4 * (x + 0.125)) - 4 * floor (x + 0.125)))
#define POSITION(x) ((int) floor (x + 0.125))
static cairo_int_status_t
composite_glyphs (void *surface,
cairo_operator_t op,
@ -1608,14 +1605,23 @@ composite_glyphs (void *surface,
op = _render_operator (op),
_cairo_xlib_surface_ensure_picture (dst);
for (i = 0; i < num_glyphs; i++) {
unsigned long xphase, yphase;
int this_x, this_y;
int old_width;
xphase = PHASE(glyphs[i].d.x);
yphase = PHASE(glyphs[i].d.y);
if (info->font->options.subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_OFF) {
int res = _cairo_subpixel_resolution (info->font->options.subpixel_positions);
unsigned long xphase, yphase;
xphase = _cairo_subpixel_phase (glyphs[i].d.x, res);
yphase = _cairo_subpixel_phase (glyphs[i].d.y, res);
glyphs[i].index |= (xphase << XSHIFT) | (yphase << YSHIFT);
glyphs[i].index |= (xphase << 24) | (yphase << 26);
this_x = _cairo_subpixel_position (glyphs[i].d.x, res);
this_y = _cairo_subpixel_position (glyphs[i].d.y, res);
}
else {
this_x = _cairo_lround (glyphs[i].d.x);
this_y = _cairo_lround (glyphs[i].d.y);
}
status = _cairo_scaled_glyph_lookup (info->font,
glyphs[i].index,
@ -1625,9 +1631,6 @@ composite_glyphs (void *surface,
if (unlikely (status))
return status;
this_x = POSITION (glyphs[i].d.x);
this_y = POSITION (glyphs[i].d.y);
/* Send unsent glyphs to the server */
if (glyph->dev_private_key != display) {
status = _cairo_xlib_surface_add_glyph (display, info->font, &glyph);

View file

@ -1437,6 +1437,27 @@ typedef enum _cairo_color_mode {
CAIRO_COLOR_MODE_COLOR
} cairo_color_mode_t;
/**
* cairo_subpixel_positions_t:
* @CAIRO_SUBPIXEL_POSITONS_DEFAULT: Use subpixel positions in
* the default manner for the font backend and target device, since 1.20
* @CAIRO_SUBPIXEL_POSITIONS_OFF: Do not use subpixel positions, since 1.20
* @CAIRO_SUBPIXEL_POSITIONS_ON: Use subpixel positions, since 1.20
* @CAIRO_SUBPIXEL_POSITIONS_FINE: Use fine subpixel positions, since 1.20
*
* Specifies whether to use subpixel positions when rendering glyphs.
* Without subpixel positions, glyph positions will be rounded to pixels.
* With subpixel positions, glyph positions rounded to a subpixel grid.
*
* Since: 1.20
*/
typedef enum _cairo_subpixel_positions {
CAIRO_SUBPIXEL_POSITIONS_DEFAULT,
CAIRO_SUBPIXEL_POSITIONS_OFF,
CAIRO_SUBPIXEL_POSITIONS_ON,
CAIRO_SUBPIXEL_POSITIONS_FINE
} cairo_subpixel_positions_t;
/**
* cairo_font_options_t:
*
@ -1542,6 +1563,13 @@ cairo_font_options_get_custom_palette_color (cairo_font_options_t *options,
double *red, double *green,
double *blue, double *alpha);
cairo_public cairo_subpixel_positions_t
cairo_font_options_get_subpixel_positions (const cairo_font_options_t *options);
cairo_public void
cairo_font_options_set_subpixel_positions (cairo_font_options_t *options,
cairo_subpixel_positions_t value);
/* This interface is for dealing with text as text, not caring about the
font object inside the cairo_t. */

View file

@ -400,10 +400,47 @@ cairo_private uintptr_t
_cairo_hash_uintptr (uintptr_t hash,
uintptr_t u);
/* We use bits 24-27 to store phases for subpixel positions */
#define _cairo_scaled_glyph_index(g) ((unsigned long)((g)->hash_entry.hash & 0xffffff))
#define _cairo_scaled_glyph_xphase(g) (int)(((g)->hash_entry.hash >> 24) & 3)
#define _cairo_scaled_glyph_yphase(g) (int)(((g)->hash_entry.hash >> 26) & 3)
/* We use the upper 8 bits to store phases for 64x64 subpixel positions */
static inline int _cairo_subpixel_bits (cairo_subpixel_positions_t subpixel_positions)
{
int bits[] = {
[CAIRO_SUBPIXEL_POSITIONS_DEFAULT] = 2,
[CAIRO_SUBPIXEL_POSITIONS_OFF] = 0,
[CAIRO_SUBPIXEL_POSITIONS_ON] = 2,
[CAIRO_SUBPIXEL_POSITIONS_FINE] = 4,
};
return bits[subpixel_positions];
}
static inline int _cairo_subpixel_resolution (cairo_subpixel_positions_t subpixel_positions)
{
return 1 << _cairo_subpixel_bits (subpixel_positions);
}
static inline int _cairo_subpixel_phase (double pos, int res)
{
double shifted_pos = pos + 1.0 / (2 * res);
return (int) (floor (res * shifted_pos) - res * floor (shifted_pos));
}
static inline double _cairo_subpixel_position (double pos, int res)
{
return floor (pos + 1.0 / (2 * res));
}
#define MAX_PHASE_BITS 4
#define XSHIFT (32 - 2 * MAX_PHASE_BITS)
#define YSHIFT (32 - MAX_PHASE_BITS)
#define PHASE_MASK ((1 << MAX_PHASE_BITS) - 1)
#define INDEX_MASK ((1 << XSHIFT) - 1)
#define _cairo_scaled_glyph_index(g) ((unsigned long)((g)->hash_entry.hash & INDEX_MASK))
#define _cairo_scaled_glyph_xphase(g) (unsigned int)(((g)->hash_entry.hash >> XSHIFT) & PHASE_MASK)
#define _cairo_scaled_glyph_yphase(g) ((unsigned int)(((g)->hash_entry.hash >> YSHIFT) & PHASE_MASK))
#define _cairo_scaled_glyph_set_index(g, i) ((g)->hash_entry.hash = (i))
#include "cairo-scaled-font-private.h"

View file

@ -1109,8 +1109,13 @@ _cairo_dwrite_scaled_font_init_glyph_color_surface(cairo_dwrite_scaled_font_t *s
FLOAT advance = 0;
UINT16 index = (UINT16)_cairo_scaled_glyph_index (scaled_glyph);
DWRITE_GLYPH_OFFSET offset;
double x = -x1 + .25 * _cairo_scaled_glyph_xphase (scaled_glyph);
double y = -y1 + .25 * _cairo_scaled_glyph_yphase (scaled_glyph);
double x = -x1;
double y = -y1;
if (scaled_font->base.options.subpixel_positions != CAIRO_SUBPIXEL_POSITIONS_OFF) {
double res = _cairo_subpixel_resolution (scaled_font->base.options.subpixel_positions);
x += _cairo_scaled_glyph_xphase (scaled_glyph) / res;
y += _cairo_scaled_glyph_yphase (scaled_glyph) / res;
}
DWRITE_MATRIX matrix;
D2D1_POINT_2F origin = {0, 0};
RefPtr<IDWriteColorGlyphRunEnumerator1> run_enumerator;