/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ /* * Copyright © 2005 Keith Packard * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * * The Original Code is the cairo graphics library. * * The Initial Developer of the Original Code is Keith Packard * * Contributor(s): * Keith Packard * Carl D. Worth * Graydon Hoare * Owen Taylor * Behdad Esfahbod * Chris Wilson */ #include "cairoint.h" #include "cairo-error-private.h" #include "cairo-scaled-font-private.h" #if _XOPEN_SOURCE >= 600 || defined (_ISOC99_SOURCE) #define ISFINITE(x) isfinite (x) #else #define ISFINITE(x) ((x) * (x) >= 0.) /* check for NaNs */ #endif /** * SECTION:cairo-scaled-font * @Title: cairo_scaled_font_t * @Short_Description: Font face at particular size and options * @See_Also: #cairo_font_face_t, #cairo_matrix_t, #cairo_font_options_t * * #cairo_scaled_font_t represents a realization of a font face at a particular * size and transformation and a certain set of font options. */ /* Global Glyph Cache * * We maintain a global pool of glyphs split between all active fonts. This * allows a heavily used individual font to cache more glyphs than we could * manage if we used per-font glyph caches, but at the same time maintains * fairness across all fonts and provides a cap on the maximum number of * global glyphs. * * The glyphs are allocated in pages, which are capped in the global pool. * Using pages means we can reduce the frequency at which we have to probe the * global pool and ameliorates the memory allocation pressure. */ /* XXX: This number is arbitrary---we've never done any measurement of this. */ #define MAX_GLYPH_PAGES_CACHED 512 static cairo_cache_t cairo_scaled_glyph_page_cache; #define CAIRO_SCALED_GLYPH_PAGE_SIZE 32 struct _cairo_scaled_glyph_page { cairo_cache_entry_t cache_entry; cairo_list_t link; unsigned int num_glyphs; cairo_scaled_glyph_t glyphs[CAIRO_SCALED_GLYPH_PAGE_SIZE]; }; /* * Notes: * * To store rasterizations of glyphs, we use an image surface and the * device offset to represent the glyph origin. * * A device_transform converts from device space (a conceptual space) to * surface space. For simple cases of translation only, it's called a * device_offset and is public API (cairo_surface_[gs]et_device_offset()). * A possibly better name for those functions could have been * cairo_surface_[gs]et_origin(). So, that's what they do: they set where * the device-space origin (0,0) is in the surface. If the origin is inside * the surface, device_offset values are positive. It may look like this: * * Device space: * (-x,-y) <-- negative numbers * +----------------+ * | . | * | . | * |......(0,0) <---|-- device-space origin * | | * | | * +----------------+ * (width-x,height-y) * * Surface space: * (0,0) <-- surface-space origin * +---------------+ * | . | * | . | * |......(x,y) <--|-- device_offset * | | * | | * +---------------+ * (width,height) * * In other words: device_offset is the coordinates of the device-space * origin relative to the top-left of the surface. * * We use device offsets in a couple of places: * * - Public API: To let toolkits like Gtk+ give user a surface that * only represents part of the final destination (say, the expose * area), but has the same device space as the destination. In these * cases device_offset is typically negative. Example: * * application window * +---------------+ * | . | * | (x,y). | * |......+---+ | * | | | <--|-- expose area * | +---+ | * +---------------+ * * In this case, the user of cairo API can set the device_space on * the expose area to (-x,-y) to move the device space origin to that * of the application window, such that drawing in the expose area * surface and painting it in the application window has the same * effect as drawing in the application window directly. Gtk+ has * been using this feature. * * - Glyph surfaces: In most font rendering systems, glyph surfaces * have an origin at (0,0) and a bounding box that is typically * represented as (x_bearing,y_bearing,width,height). Depending on * which way y progresses in the system, y_bearing may typically be * negative (for systems similar to cairo, with origin at top left), * or be positive (in systems like PDF with origin at bottom left). * No matter which is the case, it is important to note that * (x_bearing,y_bearing) is the coordinates of top-left of the glyph * relative to the glyph origin. That is, for example: * * Scaled-glyph space: * * (x_bearing,y_bearing) <-- negative numbers * +----------------+ * | . | * | . | * |......(0,0) <---|-- glyph origin * | | * | | * +----------------+ * (width+x_bearing,height+y_bearing) * * Note the similarity of the origin to the device space. That is * exactly how we use the device_offset to represent scaled glyphs: * to use the device-space origin as the glyph origin. * * Now compare the scaled-glyph space to device-space and surface-space * and convince yourself that: * * (x_bearing,y_bearing) = (-x,-y) = - device_offset * * That's right. If you are not convinced yet, contrast the definition * of the two: * * "(x_bearing,y_bearing) is the coordinates of top-left of the * glyph relative to the glyph origin." * * "In other words: device_offset is the coordinates of the * device-space origin relative to the top-left of the surface." * * and note that glyph origin = device-space origin. */ static void _cairo_scaled_font_fini_internal (cairo_scaled_font_t *scaled_font); static void _cairo_scaled_glyph_fini (cairo_scaled_font_t *scaled_font, cairo_scaled_glyph_t *scaled_glyph) { const cairo_surface_backend_t *surface_backend = scaled_font->surface_backend; if (surface_backend != NULL && surface_backend->scaled_glyph_fini != NULL) surface_backend->scaled_glyph_fini (scaled_glyph, scaled_font); if (scaled_glyph->surface != NULL) cairo_surface_destroy (&scaled_glyph->surface->base); if (scaled_glyph->path != NULL) _cairo_path_fixed_destroy (scaled_glyph->path); if (scaled_glyph->recording_surface != NULL) { cairo_surface_finish (scaled_glyph->recording_surface); cairo_surface_destroy (scaled_glyph->recording_surface); } } #define ZOMBIE 0 static const cairo_scaled_font_t _cairo_scaled_font_nil = { { ZOMBIE }, /* hash_entry */ CAIRO_STATUS_NO_MEMORY, /* status */ CAIRO_REFERENCE_COUNT_INVALID, /* ref_count */ { 0, 0, 0, NULL }, /* user_data */ NULL, /* original_font_face */ NULL, /* font_face */ { 1., 0., 0., 1., 0, 0}, /* font_matrix */ { 1., 0., 0., 1., 0, 0}, /* ctm */ { CAIRO_ANTIALIAS_DEFAULT, /* options */ CAIRO_SUBPIXEL_ORDER_DEFAULT, CAIRO_HINT_STYLE_DEFAULT, CAIRO_HINT_METRICS_DEFAULT} , FALSE, /* placeholder */ FALSE, /* holdover */ TRUE, /* finished */ { 1., 0., 0., 1., 0, 0}, /* scale */ { 1., 0., 0., 1., 0, 0}, /* scale_inverse */ 1., /* max_scale */ { 0., 0., 0., 0., 0. }, /* extents */ { 0., 0., 0., 0., 0. }, /* fs_extents */ CAIRO_MUTEX_NIL_INITIALIZER,/* mutex */ NULL, /* glyphs */ { NULL, NULL }, /* pages */ FALSE, /* cache_frozen */ FALSE, /* global_cache_frozen */ NULL, /* surface_backend */ NULL, /* surface_private */ NULL /* backend */ }; /** * _cairo_scaled_font_set_error: * @scaled_font: a scaled_font * @status: a status value indicating an error * * Atomically sets scaled_font->status to @status and calls _cairo_error; * Does nothing if status is %CAIRO_STATUS_SUCCESS. * * All assignments of an error status to scaled_font->status should happen * through _cairo_scaled_font_set_error(). Note that due to the nature of * the atomic operation, it is not safe to call this function on the nil * objects. * * The purpose of this function is to allow the user to set a * breakpoint in _cairo_error() to generate a stack trace for when the * user causes cairo to detect an error. * * Return value: the error status. **/ cairo_status_t _cairo_scaled_font_set_error (cairo_scaled_font_t *scaled_font, cairo_status_t status) { if (status == CAIRO_STATUS_SUCCESS) return status; /* Don't overwrite an existing error. This preserves the first * error, which is the most significant. */ _cairo_status_set_error (&scaled_font->status, status); return _cairo_error (status); } /** * cairo_scaled_font_get_type: * @scaled_font: a #cairo_scaled_font_t * * This function returns the type of the backend used to create * a scaled font. See #cairo_font_type_t for available types. * However, this function never returns %CAIRO_FONT_TYPE_TOY. * * Return value: The type of @scaled_font. * * Since: 1.2 **/ cairo_font_type_t cairo_scaled_font_get_type (cairo_scaled_font_t *scaled_font) { if (CAIRO_REFERENCE_COUNT_IS_INVALID (&scaled_font->ref_count)) return CAIRO_FONT_TYPE_TOY; return scaled_font->backend->type; } /** * cairo_scaled_font_status: * @scaled_font: a #cairo_scaled_font_t * * Checks whether an error has previously occurred for this * scaled_font. * * Return value: %CAIRO_STATUS_SUCCESS or another error such as * %CAIRO_STATUS_NO_MEMORY. **/ cairo_status_t cairo_scaled_font_status (cairo_scaled_font_t *scaled_font) { return scaled_font->status; } slim_hidden_def (cairo_scaled_font_status); /* Here we keep a unique mapping from * font_face/matrix/ctm/font_options => #cairo_scaled_font_t. * * Here are the things that we want to map: * * a) All otherwise referenced #cairo_scaled_font_t's * b) Some number of not otherwise referenced #cairo_scaled_font_t's * * The implementation uses a hash table which covers (a) * completely. Then, for (b) we have an array of otherwise * unreferenced fonts (holdovers) which are expired in * least-recently-used order. * * The cairo_scaled_font_create() code gets to treat this like a regular * hash table. All of the magic for the little holdover cache is in * cairo_scaled_font_reference() and cairo_scaled_font_destroy(). */ /* This defines the size of the holdover array ... that is, the number * of scaled fonts we keep around even when not otherwise referenced */ #define CAIRO_SCALED_FONT_MAX_HOLDOVERS 256 typedef struct _cairo_scaled_font_map { cairo_scaled_font_t *mru_scaled_font; cairo_hash_table_t *hash_table; cairo_scaled_font_t *holdovers[CAIRO_SCALED_FONT_MAX_HOLDOVERS]; int num_holdovers; } cairo_scaled_font_map_t; static cairo_scaled_font_map_t *cairo_scaled_font_map; static int _cairo_scaled_font_keys_equal (const void *abstract_key_a, const void *abstract_key_b); static cairo_scaled_font_map_t * _cairo_scaled_font_map_lock (void) { CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); if (cairo_scaled_font_map == NULL) { cairo_scaled_font_map = malloc (sizeof (cairo_scaled_font_map_t)); if (unlikely (cairo_scaled_font_map == NULL)) goto CLEANUP_MUTEX_LOCK; cairo_scaled_font_map->mru_scaled_font = NULL; cairo_scaled_font_map->hash_table = _cairo_hash_table_create (_cairo_scaled_font_keys_equal); if (unlikely (cairo_scaled_font_map->hash_table == NULL)) goto CLEANUP_SCALED_FONT_MAP; cairo_scaled_font_map->num_holdovers = 0; } return cairo_scaled_font_map; CLEANUP_SCALED_FONT_MAP: free (cairo_scaled_font_map); cairo_scaled_font_map = NULL; CLEANUP_MUTEX_LOCK: CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); return NULL; } static void _cairo_scaled_font_map_unlock (void) { CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); } void _cairo_scaled_font_map_destroy (void) { cairo_scaled_font_map_t *font_map; cairo_scaled_font_t *scaled_font; CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); font_map = cairo_scaled_font_map; if (unlikely (font_map == NULL)) { goto CLEANUP_MUTEX_LOCK; } scaled_font = font_map->mru_scaled_font; if (scaled_font != NULL) { CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); cairo_scaled_font_destroy (scaled_font); CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); } /* remove scaled_fonts starting from the end so that font_map->holdovers * is always in a consistent state when we release the mutex. */ while (font_map->num_holdovers) { scaled_font = font_map->holdovers[font_map->num_holdovers-1]; assert (! CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&scaled_font->ref_count)); _cairo_hash_table_remove (font_map->hash_table, &scaled_font->hash_entry); font_map->num_holdovers--; /* This releases the font_map lock to avoid the possibility of a * recursive deadlock when the scaled font destroy closure gets * called */ _cairo_scaled_font_fini (scaled_font); free (scaled_font); } _cairo_hash_table_destroy (font_map->hash_table); free (cairo_scaled_font_map); cairo_scaled_font_map = NULL; CLEANUP_MUTEX_LOCK: CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); } static void _cairo_scaled_glyph_page_destroy (void *closure) { cairo_scaled_glyph_page_t *page = closure; cairo_scaled_font_t *scaled_font; unsigned int n; scaled_font = (cairo_scaled_font_t *) page->cache_entry.hash; for (n = 0; n < page->num_glyphs; n++) { _cairo_hash_table_remove (scaled_font->glyphs, &page->glyphs[n].hash_entry); _cairo_scaled_glyph_fini (scaled_font, &page->glyphs[n]); } cairo_list_del (&page->link); free (page); } /* If a scaled font wants to unlock the font map while still being * created (needed for user-fonts), we need to take extra care not * ending up with multiple identical scaled fonts being created. * * What we do is, we create a fake identical scaled font, and mark * it as placeholder, lock its mutex, and insert that in the fontmap * hash table. This makes other code trying to create an identical * scaled font to just wait and retry. * * The reason we have to create a fake scaled font instead of just using * scaled_font is for lifecycle management: we need to (or rather, * other code needs to) reference the scaled_font in the hash table. * We can't do that on the input scaled_font as it may be freed by * font backend upon error. */ cairo_status_t _cairo_scaled_font_register_placeholder_and_unlock_font_map (cairo_scaled_font_t *scaled_font) { cairo_status_t status; cairo_scaled_font_t *placeholder_scaled_font; assert (CAIRO_MUTEX_IS_LOCKED (_cairo_scaled_font_map_mutex)); status = scaled_font->status; if (unlikely (status)) return status; placeholder_scaled_font = malloc (sizeof (cairo_scaled_font_t)); if (unlikely (placeholder_scaled_font == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); /* full initialization is wasteful, but who cares... */ status = _cairo_scaled_font_init (placeholder_scaled_font, scaled_font->font_face, &scaled_font->font_matrix, &scaled_font->ctm, &scaled_font->options, NULL); if (unlikely (status)) goto FREE_PLACEHOLDER; placeholder_scaled_font->placeholder = TRUE; status = _cairo_hash_table_insert (cairo_scaled_font_map->hash_table, &placeholder_scaled_font->hash_entry); if (unlikely (status)) goto FINI_PLACEHOLDER; CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); CAIRO_MUTEX_LOCK (placeholder_scaled_font->mutex); return CAIRO_STATUS_SUCCESS; FINI_PLACEHOLDER: _cairo_scaled_font_fini_internal (placeholder_scaled_font); FREE_PLACEHOLDER: free (placeholder_scaled_font); return _cairo_scaled_font_set_error (scaled_font, status); } void _cairo_scaled_font_unregister_placeholder_and_lock_font_map (cairo_scaled_font_t *scaled_font) { cairo_scaled_font_t *placeholder_scaled_font; CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); placeholder_scaled_font = _cairo_hash_table_lookup (cairo_scaled_font_map->hash_table, &scaled_font->hash_entry); assert (placeholder_scaled_font != NULL); assert (placeholder_scaled_font->placeholder); assert (CAIRO_MUTEX_IS_LOCKED (placeholder_scaled_font->mutex)); _cairo_hash_table_remove (cairo_scaled_font_map->hash_table, &placeholder_scaled_font->hash_entry); CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); CAIRO_MUTEX_UNLOCK (placeholder_scaled_font->mutex); cairo_scaled_font_destroy (placeholder_scaled_font); CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); } static void _cairo_scaled_font_placeholder_wait_for_creation_to_finish (cairo_scaled_font_t *placeholder_scaled_font) { /* reference the place holder so it doesn't go away */ cairo_scaled_font_reference (placeholder_scaled_font); /* now unlock the fontmap mutex so creation has a chance to finish */ CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); /* wait on placeholder mutex until we are awaken */ CAIRO_MUTEX_LOCK (placeholder_scaled_font->mutex); /* ok, creation done. just clean up and back out */ CAIRO_MUTEX_UNLOCK (placeholder_scaled_font->mutex); cairo_scaled_font_destroy (placeholder_scaled_font); CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); } /* Fowler / Noll / Vo (FNV) Hash (http://www.isthe.com/chongo/tech/comp/fnv/) * * Not necessarily better than a lot of other hashes, but should be OK, and * well tested with binary data. */ #define FNV_32_PRIME ((uint32_t)0x01000193) #define FNV1_32_INIT ((uint32_t)0x811c9dc5) static uint32_t _hash_matrix_fnv (const cairo_matrix_t *matrix, uint32_t hval) { const uint8_t *buffer = (const uint8_t *) matrix; int len = sizeof (cairo_matrix_t); do { hval *= FNV_32_PRIME; hval ^= *buffer++; } while (--len); return hval; } static uint32_t _hash_mix_bits (uint32_t hash) { hash += hash << 12; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; return hash; } static void _cairo_scaled_font_init_key (cairo_scaled_font_t *scaled_font, cairo_font_face_t *font_face, const cairo_matrix_t *font_matrix, const cairo_matrix_t *ctm, const cairo_font_options_t *options) { uint32_t hash = FNV1_32_INIT; scaled_font->status = CAIRO_STATUS_SUCCESS; scaled_font->placeholder = FALSE; scaled_font->font_face = font_face; scaled_font->font_matrix = *font_matrix; scaled_font->ctm = *ctm; /* ignore translation values in the ctm */ scaled_font->ctm.x0 = 0.; scaled_font->ctm.y0 = 0.; _cairo_font_options_init_copy (&scaled_font->options, options); /* We do a bytewise hash on the font matrices */ hash = _hash_matrix_fnv (&scaled_font->font_matrix, hash); hash = _hash_matrix_fnv (&scaled_font->ctm, hash); hash = _hash_mix_bits (hash); hash ^= (unsigned long) scaled_font->font_face; hash ^= cairo_font_options_hash (&scaled_font->options); /* final mixing of bits */ hash = _hash_mix_bits (hash); assert (hash != ZOMBIE); scaled_font->hash_entry.hash = hash; } static cairo_bool_t _cairo_scaled_font_keys_equal (const void *abstract_key_a, const void *abstract_key_b) { const cairo_scaled_font_t *key_a = abstract_key_a; const cairo_scaled_font_t *key_b = abstract_key_b; if (key_a->hash_entry.hash != key_b->hash_entry.hash) return FALSE; return key_a->font_face == key_b->font_face && memcmp ((unsigned char *)(&key_a->font_matrix.xx), (unsigned char *)(&key_b->font_matrix.xx), sizeof(cairo_matrix_t)) == 0 && memcmp ((unsigned char *)(&key_a->ctm.xx), (unsigned char *)(&key_b->ctm.xx), sizeof(cairo_matrix_t)) == 0 && cairo_font_options_equal (&key_a->options, &key_b->options); } static cairo_bool_t _cairo_scaled_font_matches (const cairo_scaled_font_t *scaled_font, const cairo_font_face_t *font_face, const cairo_matrix_t *font_matrix, const cairo_matrix_t *ctm, const cairo_font_options_t *options) { return scaled_font->original_font_face == font_face && memcmp ((unsigned char *)(&scaled_font->font_matrix.xx), (unsigned char *)(&font_matrix->xx), sizeof(cairo_matrix_t)) == 0 && memcmp ((unsigned char *)(&scaled_font->ctm.xx), (unsigned char *)(&ctm->xx), sizeof(cairo_matrix_t)) == 0 && cairo_font_options_equal (&scaled_font->options, options); } static cairo_bool_t _cairo_scaled_glyphs_equal (const void *abstract_a, const void *abstract_b) { const cairo_scaled_glyph_t *a = abstract_a; const cairo_scaled_glyph_t *b = abstract_b; return a->hash_entry.hash == b->hash_entry.hash; } /* * Basic #cairo_scaled_font_t object management */ cairo_status_t _cairo_scaled_font_init (cairo_scaled_font_t *scaled_font, cairo_font_face_t *font_face, const cairo_matrix_t *font_matrix, const cairo_matrix_t *ctm, const cairo_font_options_t *options, const cairo_scaled_font_backend_t *backend) { cairo_status_t status; status = cairo_font_options_status ((cairo_font_options_t *) options); if (unlikely (status)) return status; _cairo_scaled_font_init_key (scaled_font, font_face, font_matrix, ctm, options); cairo_matrix_multiply (&scaled_font->scale, &scaled_font->font_matrix, &scaled_font->ctm); scaled_font->max_scale = MAX (fabs (scaled_font->scale.xx) + fabs (scaled_font->scale.xy), fabs (scaled_font->scale.yx) + fabs (scaled_font->scale.yy)); scaled_font->scale_inverse = scaled_font->scale; status = cairo_matrix_invert (&scaled_font->scale_inverse); if (unlikely (status)) { /* If the font scale matrix is rank 0, just using an all-zero inverse matrix * makes everything work correctly. This make font size 0 work without * producing an error. * * FIXME: If the scale is rank 1, we still go into error mode. But then * again, that's what we do everywhere in cairo. * * Also, the check for == 0. below may be too harsh... */ if (_cairo_matrix_is_scale_0 (&scaled_font->scale)) { cairo_matrix_init (&scaled_font->scale_inverse, 0, 0, 0, 0, -scaled_font->scale.x0, -scaled_font->scale.y0); } else return status; } scaled_font->glyphs = _cairo_hash_table_create (_cairo_scaled_glyphs_equal); if (unlikely (scaled_font->glyphs == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); cairo_list_init (&scaled_font->glyph_pages); scaled_font->cache_frozen = FALSE; scaled_font->global_cache_frozen = FALSE; scaled_font->holdover = FALSE; scaled_font->finished = FALSE; CAIRO_REFERENCE_COUNT_INIT (&scaled_font->ref_count, 1); _cairo_user_data_array_init (&scaled_font->user_data); cairo_font_face_reference (font_face); scaled_font->original_font_face = NULL; CAIRO_MUTEX_INIT (scaled_font->mutex); scaled_font->surface_backend = NULL; scaled_font->surface_private = NULL; scaled_font->backend = backend; cairo_list_init (&scaled_font->link); return CAIRO_STATUS_SUCCESS; } void _cairo_scaled_font_freeze_cache (cairo_scaled_font_t *scaled_font) { /* ensure we do not modify an error object */ assert (scaled_font->status == CAIRO_STATUS_SUCCESS); CAIRO_MUTEX_LOCK (scaled_font->mutex); scaled_font->cache_frozen = TRUE; } void _cairo_scaled_font_thaw_cache (cairo_scaled_font_t *scaled_font) { scaled_font->cache_frozen = FALSE; if (scaled_font->global_cache_frozen) { CAIRO_MUTEX_LOCK (_cairo_scaled_glyph_page_cache_mutex); _cairo_cache_thaw (&cairo_scaled_glyph_page_cache); CAIRO_MUTEX_UNLOCK (_cairo_scaled_glyph_page_cache_mutex); scaled_font->global_cache_frozen = FALSE; } CAIRO_MUTEX_UNLOCK (scaled_font->mutex); } void _cairo_scaled_font_reset_cache (cairo_scaled_font_t *scaled_font) { assert (! scaled_font->cache_frozen); CAIRO_MUTEX_LOCK (_cairo_scaled_glyph_page_cache_mutex); while (! cairo_list_is_empty (&scaled_font->glyph_pages)) { _cairo_cache_remove (&cairo_scaled_glyph_page_cache, &cairo_list_first_entry (&scaled_font->glyph_pages, cairo_scaled_glyph_page_t, link)->cache_entry); } CAIRO_MUTEX_UNLOCK (_cairo_scaled_glyph_page_cache_mutex); } cairo_status_t _cairo_scaled_font_set_metrics (cairo_scaled_font_t *scaled_font, cairo_font_extents_t *fs_metrics) { cairo_status_t status; double font_scale_x, font_scale_y; scaled_font->fs_extents = *fs_metrics; status = _cairo_matrix_compute_basis_scale_factors (&scaled_font->font_matrix, &font_scale_x, &font_scale_y, 1); if (unlikely (status)) return status; /* * The font responded in unscaled units, scale by the font * matrix scale factors to get to user space */ scaled_font->extents.ascent = fs_metrics->ascent * font_scale_y; scaled_font->extents.descent = fs_metrics->descent * font_scale_y; scaled_font->extents.height = fs_metrics->height * font_scale_y; scaled_font->extents.max_x_advance = fs_metrics->max_x_advance * font_scale_x; scaled_font->extents.max_y_advance = fs_metrics->max_y_advance * font_scale_y; return CAIRO_STATUS_SUCCESS; } static void _cairo_scaled_font_fini_internal (cairo_scaled_font_t *scaled_font) { scaled_font->finished = TRUE; _cairo_scaled_font_reset_cache (scaled_font); _cairo_hash_table_destroy (scaled_font->glyphs); cairo_font_face_destroy (scaled_font->font_face); cairo_font_face_destroy (scaled_font->original_font_face); CAIRO_MUTEX_FINI (scaled_font->mutex); if (scaled_font->surface_backend != NULL && scaled_font->surface_backend->scaled_font_fini != NULL) scaled_font->surface_backend->scaled_font_fini (scaled_font); if (scaled_font->backend != NULL && scaled_font->backend->fini != NULL) scaled_font->backend->fini (scaled_font); _cairo_user_data_array_fini (&scaled_font->user_data); } /* XXX: allow multiple backends to share the font */ void _cairo_scaled_font_revoke_ownership (cairo_scaled_font_t *scaled_font) { if (scaled_font->surface_backend == NULL) return; _cairo_scaled_font_reset_cache (scaled_font); if (scaled_font->surface_backend->scaled_font_fini != NULL) scaled_font->surface_backend->scaled_font_fini (scaled_font); scaled_font->surface_backend = NULL; scaled_font->surface_private = NULL; } void _cairo_scaled_font_fini (cairo_scaled_font_t *scaled_font) { /* Release the lock to avoid the possibility of a recursive * deadlock when the scaled font destroy closure gets called. */ CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_map_mutex); _cairo_scaled_font_fini_internal (scaled_font); CAIRO_MUTEX_LOCK (_cairo_scaled_font_map_mutex); } /** * cairo_scaled_font_create: * @font_face: a #cairo_font_face_t * @font_matrix: font space to user space transformation matrix for the * font. In the simplest case of a N point font, this matrix is * just a scale by N, but it can also be used to shear the font * or stretch it unequally along the two axes. See * cairo_set_font_matrix(). * @ctm: user to device transformation matrix with which the font will * be used. * @options: options to use when getting metrics for the font and * rendering with it. * * Creates a #cairo_scaled_font_t object from a font face and matrices that * describe the size of the font and the environment in which it will * be used. * * Return value: a newly created #cairo_scaled_font_t. Destroy with * cairo_scaled_font_destroy() **/ cairo_scaled_font_t * cairo_scaled_font_create (cairo_font_face_t *font_face, const cairo_matrix_t *font_matrix, const cairo_matrix_t *ctm, const cairo_font_options_t *options) { cairo_status_t status; cairo_scaled_font_map_t *font_map; cairo_font_face_t *original_font_face = font_face; cairo_scaled_font_t key, *old = NULL, *scaled_font = NULL, *dead = NULL; double det; status = font_face->status; if (unlikely (status)) return _cairo_scaled_font_create_in_error (status); det = _cairo_matrix_compute_determinant (font_matrix); if (! ISFINITE (det)) return _cairo_scaled_font_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_MATRIX)); det = _cairo_matrix_compute_determinant (ctm); if (! ISFINITE (det)) return _cairo_scaled_font_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_MATRIX)); status = cairo_font_options_status ((cairo_font_options_t *) options); if (unlikely (status)) return _cairo_scaled_font_create_in_error (status); /* Note that degenerate ctm or font_matrix *are* allowed. * We want to support a font size of 0. */ font_map = _cairo_scaled_font_map_lock (); if (unlikely (font_map == NULL)) return _cairo_scaled_font_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY)); scaled_font = font_map->mru_scaled_font; if (scaled_font != NULL && _cairo_scaled_font_matches (scaled_font, font_face, font_matrix, ctm, options)) { assert (scaled_font->hash_entry.hash != ZOMBIE); assert (! scaled_font->placeholder); if (likely (scaled_font->status == CAIRO_STATUS_SUCCESS)) { /* We increment the reference count manually here, (rather * than calling into cairo_scaled_font_reference), since we * must modify the reference count while our lock is still * held. */ _cairo_reference_count_inc (&scaled_font->ref_count); _cairo_scaled_font_map_unlock (); return scaled_font; } /* the font has been put into an error status - abandon the cache */ _cairo_hash_table_remove (font_map->hash_table, &scaled_font->hash_entry); scaled_font->hash_entry.hash = ZOMBIE; dead = scaled_font; font_map->mru_scaled_font = NULL; if (font_face->backend->get_implementation != NULL) { font_face = font_face->backend->get_implementation (font_face, font_matrix, ctm, options); if (unlikely (font_face->status)) { _cairo_scaled_font_map_unlock (); cairo_scaled_font_destroy (scaled_font); return _cairo_scaled_font_create_in_error (font_face->status); } } _cairo_scaled_font_init_key (&key, font_face, font_matrix, ctm, options); } else { if (font_face->backend->get_implementation != NULL) { font_face = font_face->backend->get_implementation (font_face, font_matrix, ctm, options); if (unlikely (font_face->status)) { _cairo_scaled_font_map_unlock (); return _cairo_scaled_font_create_in_error (font_face->status); } } _cairo_scaled_font_init_key (&key, font_face, font_matrix, ctm, options); while ((scaled_font = _cairo_hash_table_lookup (font_map->hash_table, &key.hash_entry))) { if (! scaled_font->placeholder) break; /* If the scaled font is being created (happens for user-font), * just wait until it's done, then retry */ _cairo_scaled_font_placeholder_wait_for_creation_to_finish (scaled_font); } /* Return existing scaled_font if it exists in the hash table. */ if (scaled_font != NULL) { /* If the original reference count is 0, then this font must have * been found in font_map->holdovers, (which means this caching is * actually working). So now we remove it from the holdovers * array, unless we caught the font in the middle of destruction. */ if (! CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&scaled_font->ref_count)) { if (scaled_font->holdover) { int i; for (i = 0; i < font_map->num_holdovers; i++) { if (font_map->holdovers[i] == scaled_font) { font_map->num_holdovers--; memmove (&font_map->holdovers[i], &font_map->holdovers[i+1], (font_map->num_holdovers - i) * sizeof (cairo_scaled_font_t*)); break; } } scaled_font->holdover = FALSE; } /* reset any error status */ scaled_font->status = CAIRO_STATUS_SUCCESS; } if (likely (scaled_font->status == CAIRO_STATUS_SUCCESS)) { /* We increment the reference count manually here, (rather * than calling into cairo_scaled_font_reference), since we * must modify the reference count while our lock is still * held. */ old = font_map->mru_scaled_font; font_map->mru_scaled_font = scaled_font; /* increment reference count for the mru cache */ _cairo_reference_count_inc (&scaled_font->ref_count); /* and increment for the returned reference */ _cairo_reference_count_inc (&scaled_font->ref_count); _cairo_scaled_font_map_unlock (); cairo_scaled_font_destroy (old); if (font_face != original_font_face) cairo_font_face_destroy (font_face); return scaled_font; } /* the font has been put into an error status - abandon the cache */ _cairo_hash_table_remove (font_map->hash_table, &scaled_font->hash_entry); scaled_font->hash_entry.hash = ZOMBIE; } } /* Otherwise create it and insert it into the hash table. */ status = font_face->backend->scaled_font_create (font_face, font_matrix, ctm, options, &scaled_font); /* Did we leave the backend in an error state? */ if (unlikely (status)) { _cairo_scaled_font_map_unlock (); if (font_face != original_font_face) cairo_font_face_destroy (font_face); if (dead != NULL) cairo_scaled_font_destroy (dead); status = _cairo_font_face_set_error (font_face, status); return _cairo_scaled_font_create_in_error (status); } /* Or did we encounter an error whilst constructing the scaled font? */ if (unlikely (scaled_font->status)) { _cairo_scaled_font_map_unlock (); if (font_face != original_font_face) cairo_font_face_destroy (font_face); if (dead != NULL) cairo_scaled_font_destroy (dead); return scaled_font; } /* Our caching above is defeated if the backend switches fonts on us - * e.g. old incarnations of toy-font-face and lazily resolved * ft-font-faces */ assert (scaled_font->font_face == font_face); scaled_font->original_font_face = cairo_font_face_reference (original_font_face); status = _cairo_hash_table_insert (font_map->hash_table, &scaled_font->hash_entry); if (likely (status == CAIRO_STATUS_SUCCESS)) { old = font_map->mru_scaled_font; font_map->mru_scaled_font = scaled_font; _cairo_reference_count_inc (&scaled_font->ref_count); } _cairo_scaled_font_map_unlock (); cairo_scaled_font_destroy (old); if (font_face != original_font_face) cairo_font_face_destroy (font_face); if (dead != NULL) cairo_scaled_font_destroy (dead); if (unlikely (status)) { /* We can't call _cairo_scaled_font_destroy here since it expects * that the font has already been successfully inserted into the * hash table. */ _cairo_scaled_font_fini_internal (scaled_font); free (scaled_font); return _cairo_scaled_font_create_in_error (status); } return scaled_font; } slim_hidden_def (cairo_scaled_font_create); static cairo_scaled_font_t *_cairo_scaled_font_nil_objects[CAIRO_STATUS_LAST_STATUS + 1]; /* XXX This should disappear in favour of a common pool of error objects. */ cairo_scaled_font_t * _cairo_scaled_font_create_in_error (cairo_status_t status) { cairo_scaled_font_t *scaled_font; assert (status != CAIRO_STATUS_SUCCESS); if (status == CAIRO_STATUS_NO_MEMORY) return (cairo_scaled_font_t *) &_cairo_scaled_font_nil; CAIRO_MUTEX_LOCK (_cairo_scaled_font_error_mutex); scaled_font = _cairo_scaled_font_nil_objects[status]; if (unlikely (scaled_font == NULL)) { scaled_font = malloc (sizeof (cairo_scaled_font_t)); if (unlikely (scaled_font == NULL)) { CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_error_mutex); _cairo_error_throw (CAIRO_STATUS_NO_MEMORY); return (cairo_scaled_font_t *) &_cairo_scaled_font_nil; } *scaled_font = _cairo_scaled_font_nil; scaled_font->status = status; _cairo_scaled_font_nil_objects[status] = scaled_font; } CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_error_mutex); return scaled_font; } void _cairo_scaled_font_reset_static_data (void) { int status; CAIRO_MUTEX_LOCK (_cairo_scaled_font_error_mutex); for (status = CAIRO_STATUS_SUCCESS; status <= CAIRO_STATUS_LAST_STATUS; status++) { if (_cairo_scaled_font_nil_objects[status] != NULL) { free (_cairo_scaled_font_nil_objects[status]); _cairo_scaled_font_nil_objects[status] = NULL; } } CAIRO_MUTEX_UNLOCK (_cairo_scaled_font_error_mutex); CAIRO_MUTEX_LOCK (_cairo_scaled_glyph_page_cache_mutex); if (cairo_scaled_glyph_page_cache.hash_table != NULL) { _cairo_cache_fini (&cairo_scaled_glyph_page_cache); cairo_scaled_glyph_page_cache.hash_table = NULL; } CAIRO_MUTEX_UNLOCK (_cairo_scaled_glyph_page_cache_mutex); } /** * cairo_scaled_font_reference: * @scaled_font: a #cairo_scaled_font_t, (may be %NULL in which case * this function does nothing) * * Increases the reference count on @scaled_font by one. This prevents * @scaled_font from being destroyed until a matching call to * cairo_scaled_font_destroy() is made. * * The number of references to a #cairo_scaled_font_t can be get using * cairo_scaled_font_get_reference_count(). * * Returns: the referenced #cairo_scaled_font_t **/ cairo_scaled_font_t * cairo_scaled_font_reference (cairo_scaled_font_t *scaled_font) { if (scaled_font == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&scaled_font->ref_count)) return scaled_font; assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&scaled_font->ref_count)); _cairo_reference_count_inc (&scaled_font->ref_count); return scaled_font; } slim_hidden_def (cairo_scaled_font_reference); /** * cairo_scaled_font_destroy: * @scaled_font: a #cairo_scaled_font_t * * Decreases the reference count on @font by one. If the result * is zero, then @font and all associated resources are freed. * See cairo_scaled_font_reference(). **/ void cairo_scaled_font_destroy (cairo_scaled_font_t *scaled_font) { cairo_scaled_font_t *lru = NULL; cairo_scaled_font_map_t *font_map; assert (CAIRO_MUTEX_IS_UNLOCKED (_cairo_scaled_font_map_mutex)); if (scaled_font == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&scaled_font->ref_count)) return; assert (CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&scaled_font->ref_count)); if (! _cairo_reference_count_dec_and_test (&scaled_font->ref_count)) return; font_map = _cairo_scaled_font_map_lock (); assert (font_map != NULL); /* Another thread may have resurrected the font whilst we waited */ if (! CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&scaled_font->ref_count)) { if (! scaled_font->placeholder && scaled_font->hash_entry.hash != ZOMBIE) { /* Another thread may have already inserted us into the holdovers */ if (scaled_font->holdover) goto unlock; /* Rather than immediately destroying this object, we put it into * the font_map->holdovers array in case it will get used again * soon (and is why we must hold the lock over the atomic op on * the reference count). To make room for it, we do actually * destroy the least-recently-used holdover. */ if (font_map->num_holdovers == CAIRO_SCALED_FONT_MAX_HOLDOVERS) { lru = font_map->holdovers[0]; assert (! CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&lru->ref_count)); _cairo_hash_table_remove (font_map->hash_table, &lru->hash_entry); font_map->num_holdovers--; memmove (&font_map->holdovers[0], &font_map->holdovers[1], font_map->num_holdovers * sizeof (cairo_scaled_font_t*)); } font_map->holdovers[font_map->num_holdovers++] = scaled_font; scaled_font->holdover = TRUE; } else lru = scaled_font; } unlock: _cairo_scaled_font_map_unlock (); /* If we pulled an item from the holdovers array, (while the font * map lock was held, of course), then there is no way that anyone * else could have acquired a reference to it. So we can now * safely call fini on it without any lock held. This is desirable * as we never want to call into any backend function with a lock * held. */ if (lru != NULL) { _cairo_scaled_font_fini_internal (lru); free (lru); } } slim_hidden_def (cairo_scaled_font_destroy); /** * cairo_scaled_font_get_reference_count: * @scaled_font: a #cairo_scaled_font_t * * Returns the current reference count of @scaled_font. * * Return value: the current reference count of @scaled_font. If the * object is a nil object, 0 will be returned. * * Since: 1.4 **/ unsigned int cairo_scaled_font_get_reference_count (cairo_scaled_font_t *scaled_font) { if (scaled_font == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&scaled_font->ref_count)) return 0; return CAIRO_REFERENCE_COUNT_GET_VALUE (&scaled_font->ref_count); } /** * cairo_scaled_font_get_user_data: * @scaled_font: a #cairo_scaled_font_t * @key: the address of the #cairo_user_data_key_t the user data was * attached to * * Return user data previously attached to @scaled_font using the * specified key. If no user data has been attached with the given * key this function returns %NULL. * * Return value: the user data previously attached or %NULL. * * Since: 1.4 **/ void * cairo_scaled_font_get_user_data (cairo_scaled_font_t *scaled_font, const cairo_user_data_key_t *key) { return _cairo_user_data_array_get_data (&scaled_font->user_data, key); } slim_hidden_def (cairo_scaled_font_get_user_data); /** * cairo_scaled_font_set_user_data: * @scaled_font: a #cairo_scaled_font_t * @key: the address of a #cairo_user_data_key_t to attach the user data to * @user_data: the user data to attach to the #cairo_scaled_font_t * @destroy: a #cairo_destroy_func_t which will be called when the * #cairo_t is destroyed or when new user data is attached using the * same key. * * Attach user data to @scaled_font. To remove user data from a surface, * call this function with the key that was used to set it and %NULL * for @data. * * Return value: %CAIRO_STATUS_SUCCESS or %CAIRO_STATUS_NO_MEMORY if a * slot could not be allocated for the user data. * * Since: 1.4 **/ cairo_status_t cairo_scaled_font_set_user_data (cairo_scaled_font_t *scaled_font, const cairo_user_data_key_t *key, void *user_data, cairo_destroy_func_t destroy) { if (CAIRO_REFERENCE_COUNT_IS_INVALID (&scaled_font->ref_count)) return scaled_font->status; return _cairo_user_data_array_set_data (&scaled_font->user_data, key, user_data, destroy); } slim_hidden_def (cairo_scaled_font_set_user_data); /* Public font API follows. */ /** * cairo_scaled_font_extents: * @scaled_font: a #cairo_scaled_font_t * @extents: a #cairo_font_extents_t which to store the retrieved extents. * * Gets the metrics for a #cairo_scaled_font_t. **/ void cairo_scaled_font_extents (cairo_scaled_font_t *scaled_font, cairo_font_extents_t *extents) { if (scaled_font->status) { extents->ascent = 0.0; extents->descent = 0.0; extents->height = 0.0; extents->max_x_advance = 0.0; extents->max_y_advance = 0.0; return; } *extents = scaled_font->extents; } slim_hidden_def (cairo_scaled_font_extents); /** * cairo_scaled_font_text_extents: * @scaled_font: a #cairo_scaled_font_t * @utf8: a NUL-terminated string of text, encoded in UTF-8 * @extents: a #cairo_text_extents_t which to store the retrieved extents. * * Gets the extents for a string of text. The extents describe a * user-space rectangle that encloses the "inked" portion of the text * drawn at the origin (0,0) (as it would be drawn by cairo_show_text() * if the cairo graphics state were set to the same font_face, * font_matrix, ctm, and font_options as @scaled_font). Additionally, * the x_advance and y_advance values indicate the amount by which the * current point would be advanced by cairo_show_text(). * * Note that whitespace characters do not directly contribute to the * size of the rectangle (extents.width and extents.height). They do * contribute indirectly by changing the position of non-whitespace * characters. In particular, trailing whitespace characters are * likely to not affect the size of the rectangle, though they will * affect the x_advance and y_advance values. * * Since: 1.2 **/ void cairo_scaled_font_text_extents (cairo_scaled_font_t *scaled_font, const char *utf8, cairo_text_extents_t *extents) { cairo_status_t status; cairo_glyph_t *glyphs = NULL; int num_glyphs; if (scaled_font->status) goto ZERO_EXTENTS; if (utf8 == NULL) goto ZERO_EXTENTS; status = cairo_scaled_font_text_to_glyphs (scaled_font, 0., 0., utf8, -1, &glyphs, &num_glyphs, NULL, NULL, NULL); if (unlikely (status)) { status = _cairo_scaled_font_set_error (scaled_font, status); goto ZERO_EXTENTS; } cairo_scaled_font_glyph_extents (scaled_font, glyphs, num_glyphs, extents); free (glyphs); return; ZERO_EXTENTS: extents->x_bearing = 0.0; extents->y_bearing = 0.0; extents->width = 0.0; extents->height = 0.0; extents->x_advance = 0.0; extents->y_advance = 0.0; } /** * cairo_scaled_font_glyph_extents: * @scaled_font: a #cairo_scaled_font_t * @glyphs: an array of glyph IDs with X and Y offsets. * @num_glyphs: the number of glyphs in the @glyphs array * @extents: a #cairo_text_extents_t which to store the retrieved extents. * * Gets the extents for an array of glyphs. The extents describe a * user-space rectangle that encloses the "inked" portion of the * glyphs, (as they would be drawn by cairo_show_glyphs() if the cairo * graphics state were set to the same font_face, font_matrix, ctm, * and font_options as @scaled_font). Additionally, the x_advance and * y_advance values indicate the amount by which the current point * would be advanced by cairo_show_glyphs(). * * Note that whitespace glyphs do not contribute to the size of the * rectangle (extents.width and extents.height). **/ void cairo_scaled_font_glyph_extents (cairo_scaled_font_t *scaled_font, const cairo_glyph_t *glyphs, int num_glyphs, cairo_text_extents_t *extents) { cairo_status_t status; int i; double min_x = 0.0, min_y = 0.0, max_x = 0.0, max_y = 0.0; cairo_bool_t visible = FALSE; cairo_scaled_glyph_t *scaled_glyph = NULL; extents->x_bearing = 0.0; extents->y_bearing = 0.0; extents->width = 0.0; extents->height = 0.0; extents->x_advance = 0.0; extents->y_advance = 0.0; if (unlikely (scaled_font->status)) goto ZERO_EXTENTS; if (num_glyphs == 0) goto ZERO_EXTENTS; if (unlikely (num_glyphs < 0)) { _cairo_error_throw (CAIRO_STATUS_NEGATIVE_COUNT); /* XXX Can't propagate error */ goto ZERO_EXTENTS; } if (unlikely (glyphs == NULL)) { _cairo_error_throw (CAIRO_STATUS_NULL_POINTER); /* XXX Can't propagate error */ goto ZERO_EXTENTS; } _cairo_scaled_font_freeze_cache (scaled_font); for (i = 0; i < num_glyphs; i++) { double left, top, right, bottom; status = _cairo_scaled_glyph_lookup (scaled_font, glyphs[i].index, CAIRO_SCALED_GLYPH_INFO_METRICS, &scaled_glyph); if (unlikely (status)) { status = _cairo_scaled_font_set_error (scaled_font, status); goto UNLOCK; } /* "Ink" extents should skip "invisible" glyphs */ if (scaled_glyph->metrics.width == 0 || scaled_glyph->metrics.height == 0) continue; left = scaled_glyph->metrics.x_bearing + glyphs[i].x; right = left + scaled_glyph->metrics.width; top = scaled_glyph->metrics.y_bearing + glyphs[i].y; bottom = top + scaled_glyph->metrics.height; if (!visible) { visible = TRUE; min_x = left; max_x = right; min_y = top; max_y = bottom; } else { if (left < min_x) min_x = left; if (right > max_x) max_x = right; if (top < min_y) min_y = top; if (bottom > max_y) max_y = bottom; } } if (visible) { extents->x_bearing = min_x - glyphs[0].x; extents->y_bearing = min_y - glyphs[0].y; extents->width = max_x - min_x; extents->height = max_y - min_y; } else { extents->x_bearing = 0.0; extents->y_bearing = 0.0; extents->width = 0.0; extents->height = 0.0; } if (num_glyphs) { double x0, y0, x1, y1; x0 = glyphs[0].x; y0 = glyphs[0].y; /* scaled_glyph contains the glyph for num_glyphs - 1 already. */ x1 = glyphs[num_glyphs - 1].x + scaled_glyph->metrics.x_advance; y1 = glyphs[num_glyphs - 1].y + scaled_glyph->metrics.y_advance; extents->x_advance = x1 - x0; extents->y_advance = y1 - y0; } else { extents->x_advance = 0.0; extents->y_advance = 0.0; } UNLOCK: _cairo_scaled_font_thaw_cache (scaled_font); return; ZERO_EXTENTS: extents->x_bearing = 0.0; extents->y_bearing = 0.0; extents->width = 0.0; extents->height = 0.0; extents->x_advance = 0.0; extents->y_advance = 0.0; } slim_hidden_def (cairo_scaled_font_glyph_extents); #define GLYPH_LUT_SIZE 64 static cairo_status_t cairo_scaled_font_text_to_glyphs_internal_cached (cairo_scaled_font_t *scaled_font, double x, double y, const char *utf8, cairo_glyph_t *glyphs, cairo_text_cluster_t **clusters, int num_chars) { struct glyph_lut_elt { unsigned long index; double x_advance; double y_advance; } glyph_lut[GLYPH_LUT_SIZE]; uint32_t glyph_lut_unicode[GLYPH_LUT_SIZE]; cairo_status_t status; const char *p; int i; for (i = 0; i < GLYPH_LUT_SIZE; i++) glyph_lut_unicode[i] = ~0U; p = utf8; for (i = 0; i < num_chars; i++) { int idx, num_bytes; uint32_t unicode; cairo_scaled_glyph_t *scaled_glyph; struct glyph_lut_elt *glyph_slot; num_bytes = _cairo_utf8_get_char_validated (p, &unicode); p += num_bytes; glyphs[i].x = x; glyphs[i].y = y; idx = unicode % ARRAY_LENGTH (glyph_lut); glyph_slot = &glyph_lut[idx]; if (glyph_lut_unicode[idx] == unicode) { glyphs[i].index = glyph_slot->index; x += glyph_slot->x_advance; y += glyph_slot->y_advance; } else { unsigned long g; g = scaled_font->backend->ucs4_to_index (scaled_font, unicode); status = _cairo_scaled_glyph_lookup (scaled_font, g, CAIRO_SCALED_GLYPH_INFO_METRICS, &scaled_glyph); if (unlikely (status)) return status; x += scaled_glyph->metrics.x_advance; y += scaled_glyph->metrics.y_advance; glyph_lut_unicode[idx] = unicode; glyph_slot->index = g; glyph_slot->x_advance = scaled_glyph->metrics.x_advance; glyph_slot->y_advance = scaled_glyph->metrics.y_advance; glyphs[i].index = g; } if (clusters) { (*clusters)[i].num_bytes = num_bytes; (*clusters)[i].num_glyphs = 1; } } return CAIRO_STATUS_SUCCESS; } static cairo_status_t cairo_scaled_font_text_to_glyphs_internal_uncached (cairo_scaled_font_t *scaled_font, double x, double y, const char *utf8, cairo_glyph_t *glyphs, cairo_text_cluster_t **clusters, int num_chars) { const char *p; int i; p = utf8; for (i = 0; i < num_chars; i++) { unsigned long g; int num_bytes; uint32_t unicode; cairo_scaled_glyph_t *scaled_glyph; cairo_status_t status; num_bytes = _cairo_utf8_get_char_validated (p, &unicode); p += num_bytes; glyphs[i].x = x; glyphs[i].y = y; g = scaled_font->backend->ucs4_to_index (scaled_font, unicode); /* * No advance needed for a single character string. So, let's speed up * one-character strings by skipping glyph lookup. */ if (num_chars > 1) { status = _cairo_scaled_glyph_lookup (scaled_font, g, CAIRO_SCALED_GLYPH_INFO_METRICS, &scaled_glyph); if (unlikely (status)) return status; x += scaled_glyph->metrics.x_advance; y += scaled_glyph->metrics.y_advance; } glyphs[i].index = g; if (clusters) { (*clusters)[i].num_bytes = num_bytes; (*clusters)[i].num_glyphs = 1; } } return CAIRO_STATUS_SUCCESS; } /** * cairo_scaled_font_text_to_glyphs: * @x: X position to place first glyph * @y: Y position to place first glyph * @scaled_font: a #cairo_scaled_font_t * @utf8: a string of text encoded in UTF-8 * @utf8_len: length of @utf8 in bytes, or -1 if it is NUL-terminated * @glyphs: pointer to array of glyphs to fill * @num_glyphs: pointer to number of glyphs * @clusters: pointer to array of cluster mapping information to fill, or %NULL * @num_clusters: pointer to number of clusters, or %NULL * @cluster_flags: pointer to location to store cluster flags corresponding to the * output @clusters, or %NULL * * Converts UTF-8 text to an array of glyphs, optionally with cluster * mapping, that can be used to render later using @scaled_font. * * If @glyphs initially points to a non-%NULL value, that array is used * as a glyph buffer, and @num_glyphs should point to the number of glyph * entries available there. If the provided glyph array is too short for * the conversion, a new glyph array is allocated using cairo_glyph_allocate() * and placed in @glyphs. Upon return, @num_glyphs always contains the * number of generated glyphs. If the value @glyphs points to has changed * after the call, the user is responsible for freeing the allocated glyph * array using cairo_glyph_free(). This may happen even if the provided * array was large enough. * * If @clusters is not %NULL, @num_clusters and @cluster_flags should not be %NULL, * and cluster mapping will be computed. * The semantics of how cluster array allocation works is similar to the glyph * array. That is, * if @clusters initially points to a non-%NULL value, that array is used * as a cluster buffer, and @num_clusters should point to the number of cluster * entries available there. If the provided cluster array is too short for * the conversion, a new cluster array is allocated using cairo_text_cluster_allocate() * and placed in @clusters. Upon return, @num_clusters always contains the * number of generated clusters. If the value @clusters points at has changed * after the call, the user is responsible for freeing the allocated cluster * array using cairo_text_cluster_free(). This may happen even if the provided * array was large enough. * * In the simplest case, @glyphs and @clusters can point to %NULL initially * and a suitable array will be allocated. In code: * * cairo_status_t status; * * cairo_glyph_t *glyphs = NULL; * int num_glyphs; * cairo_text_cluster_t *clusters = NULL; * int num_clusters; * cairo_text_cluster_flags_t cluster_flags; * * status = cairo_scaled_font_text_to_glyphs (scaled_font, * x, y, * utf8, utf8_len, * &glyphs, &num_glyphs, * &clusters, &num_clusters, &cluster_flags); * * if (status == CAIRO_STATUS_SUCCESS) { * cairo_show_text_glyphs (cr, * utf8, utf8_len, * glyphs, num_glyphs, * clusters, num_clusters, cluster_flags); * * cairo_glyph_free (glyphs); * cairo_text_cluster_free (clusters); * } * * * If no cluster mapping is needed: * * cairo_status_t status; * * cairo_glyph_t *glyphs = NULL; * int num_glyphs; * * status = cairo_scaled_font_text_to_glyphs (scaled_font, * x, y, * utf8, utf8_len, * &glyphs, &num_glyphs, * NULL, NULL, * NULL); * * if (status == CAIRO_STATUS_SUCCESS) { * cairo_show_glyphs (cr, glyphs, num_glyphs); * cairo_glyph_free (glyphs); * } * * * If stack-based glyph and cluster arrays are to be used for small * arrays: * * cairo_status_t status; * * cairo_glyph_t stack_glyphs[40]; * cairo_glyph_t *glyphs = stack_glyphs; * int num_glyphs = sizeof (stack_glyphs) / sizeof (stack_glyphs[0]); * cairo_text_cluster_t stack_clusters[40]; * cairo_text_cluster_t *clusters = stack_clusters; * int num_clusters = sizeof (stack_clusters) / sizeof (stack_clusters[0]); * cairo_text_cluster_flags_t cluster_flags; * * status = cairo_scaled_font_text_to_glyphs (scaled_font, * x, y, * utf8, utf8_len, * &glyphs, &num_glyphs, * &clusters, &num_clusters, &cluster_flags); * * if (status == CAIRO_STATUS_SUCCESS) { * cairo_show_text_glyphs (cr, * utf8, utf8_len, * glyphs, num_glyphs, * clusters, num_clusters, cluster_flags); * * if (glyphs != stack_glyphs) * cairo_glyph_free (glyphs); * if (clusters != stack_clusters) * cairo_text_cluster_free (clusters); * } * * * For details of how @clusters, @num_clusters, and @cluster_flags map input * UTF-8 text to the output glyphs see cairo_show_text_glyphs(). * * The output values can be readily passed to cairo_show_text_glyphs() * cairo_show_glyphs(), or related functions, assuming that the exact * same @scaled_font is used for the operation. * * Return value: %CAIRO_STATUS_SUCCESS upon success, or an error status * if the input values are wrong or if conversion failed. If the input * values are correct but the conversion failed, the error status is also * set on @scaled_font. * * Since: 1.8 **/ #define CACHING_THRESHOLD 16 cairo_status_t cairo_scaled_font_text_to_glyphs (cairo_scaled_font_t *scaled_font, double x, double y, const char *utf8, int utf8_len, cairo_glyph_t **glyphs, int *num_glyphs, cairo_text_cluster_t **clusters, int *num_clusters, cairo_text_cluster_flags_t *cluster_flags) { int num_chars = 0; cairo_status_t status; cairo_glyph_t *orig_glyphs; cairo_text_cluster_t *orig_clusters; status = scaled_font->status; if (unlikely (status)) return status; /* A slew of sanity checks */ /* glyphs and num_glyphs can't be NULL */ if (glyphs == NULL || num_glyphs == NULL) { status = _cairo_error (CAIRO_STATUS_NULL_POINTER); goto BAIL; } /* Special case for NULL and -1 */ if (utf8 == NULL && utf8_len == -1) utf8_len = 0; /* No NULLs for non-NULLs! */ if ((utf8_len && utf8 == NULL) || (clusters && num_clusters == NULL) || (clusters && cluster_flags == NULL)) { status = _cairo_error (CAIRO_STATUS_NULL_POINTER); goto BAIL; } /* A -1 for utf8_len means NUL-terminated */ if (utf8_len == -1) utf8_len = strlen (utf8); /* A NULL *glyphs means no prealloced glyphs array */ if (glyphs && *glyphs == NULL) *num_glyphs = 0; /* A NULL *clusters means no prealloced clusters array */ if (clusters && *clusters == NULL) *num_clusters = 0; if (!clusters && num_clusters) { num_clusters = NULL; } if (cluster_flags) { *cluster_flags = FALSE; } if (!clusters && cluster_flags) { cluster_flags = NULL; } /* Apart from that, no negatives */ if (utf8_len < 0 || *num_glyphs < 0 || (num_clusters && *num_clusters < 0)) { status = _cairo_error (CAIRO_STATUS_NEGATIVE_COUNT); goto BAIL; } if (utf8_len == 0) { status = CAIRO_STATUS_SUCCESS; goto BAIL; } /* validate input so backend does not have to */ status = _cairo_utf8_to_ucs4 (utf8, utf8_len, NULL, &num_chars); if (unlikely (status)) goto BAIL; _cairo_scaled_font_freeze_cache (scaled_font); orig_glyphs = *glyphs; orig_clusters = clusters ? *clusters : NULL; if (scaled_font->backend->text_to_glyphs) { status = scaled_font->backend->text_to_glyphs (scaled_font, x, y, utf8, utf8_len, glyphs, num_glyphs, clusters, num_clusters, cluster_flags); if (status != CAIRO_INT_STATUS_UNSUPPORTED) { if (status == CAIRO_STATUS_SUCCESS) { /* The checks here are crude; we only should do them in * user-font backend, but they don't hurt here. This stuff * can be hard to get right. */ if (*num_glyphs < 0) { status = _cairo_error (CAIRO_STATUS_NEGATIVE_COUNT); goto DONE; } if (num_glyphs && *glyphs == NULL) { status = _cairo_error (CAIRO_STATUS_NULL_POINTER); goto DONE; } if (clusters) { if (*num_clusters < 0) { status = _cairo_error (CAIRO_STATUS_NEGATIVE_COUNT); goto DONE; } if (num_clusters && *clusters == NULL) { status = _cairo_error (CAIRO_STATUS_NULL_POINTER); goto DONE; } /* Don't trust the backend, validate clusters! */ status = _cairo_validate_text_clusters (utf8, utf8_len, *glyphs, *num_glyphs, *clusters, *num_clusters, *cluster_flags); } } goto DONE; } } if (*num_glyphs < num_chars) { *glyphs = cairo_glyph_allocate (num_chars); if (unlikely (*glyphs == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto DONE; } } *num_glyphs = num_chars; if (clusters) { if (*num_clusters < num_chars) { *clusters = cairo_text_cluster_allocate (num_chars); if (unlikely (*clusters == NULL)) { status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto DONE; } } *num_clusters = num_chars; } if (num_chars > CACHING_THRESHOLD) status = cairo_scaled_font_text_to_glyphs_internal_cached (scaled_font, x, y, utf8, *glyphs, clusters, num_chars); else status = cairo_scaled_font_text_to_glyphs_internal_uncached (scaled_font, x, y, utf8, *glyphs, clusters, num_chars); DONE: /* error that should be logged on scaled_font happened */ _cairo_scaled_font_thaw_cache (scaled_font); if (unlikely (status)) { *num_glyphs = 0; if (*glyphs != orig_glyphs) { cairo_glyph_free (*glyphs); *glyphs = orig_glyphs; } if (clusters) { *num_clusters = 0; if (*clusters != orig_clusters) { cairo_text_cluster_free (*clusters); *clusters = orig_clusters; } } } return _cairo_scaled_font_set_error (scaled_font, status); BAIL: /* error with input arguments */ if (num_glyphs) *num_glyphs = 0; if (num_clusters) *num_clusters = 0; return status; } slim_hidden_def (cairo_scaled_font_text_to_glyphs); static inline cairo_bool_t _range_contains_glyph (const cairo_box_t *extents, cairo_fixed_t left, cairo_fixed_t top, cairo_fixed_t right, cairo_fixed_t bottom) { return right > extents->p1.x && left < extents->p2.x && bottom > extents->p1.y && top < extents->p2.y; } /* * Compute a device-space bounding box for the glyphs. */ cairo_status_t _cairo_scaled_font_glyph_device_extents (cairo_scaled_font_t *scaled_font, const cairo_glyph_t *glyphs, int num_glyphs, cairo_rectangle_int_t *extents, cairo_bool_t *overlap_out) { cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_box_t box = { { INT_MAX, INT_MAX }, { INT_MIN, INT_MIN }}; cairo_scaled_glyph_t *glyph_cache[64]; cairo_bool_t overlap = overlap_out ? FALSE : TRUE; cairo_round_glyph_positions_t round_glyph_positions = _cairo_font_options_get_round_glyph_positions (&scaled_font->options); int i; if (unlikely (scaled_font->status)) return scaled_font->status; _cairo_scaled_font_freeze_cache (scaled_font); memset (glyph_cache, 0, sizeof (glyph_cache)); for (i = 0; i < num_glyphs; i++) { cairo_scaled_glyph_t *scaled_glyph; cairo_fixed_t x, y, x1, y1, x2, y2; int cache_index = glyphs[i].index % ARRAY_LENGTH (glyph_cache); scaled_glyph = glyph_cache[cache_index]; if (scaled_glyph == NULL || _cairo_scaled_glyph_index (scaled_glyph) != glyphs[i].index) { status = _cairo_scaled_glyph_lookup (scaled_font, glyphs[i].index, CAIRO_SCALED_GLYPH_INFO_METRICS, &scaled_glyph); if (unlikely (status)) break; glyph_cache[cache_index] = scaled_glyph; } if (round_glyph_positions == CAIRO_ROUND_GLYPH_POS_ON) x = _cairo_fixed_from_double (_cairo_lround (glyphs[i].x)); else x = _cairo_fixed_from_double (glyphs[i].x); x1 = x + scaled_glyph->bbox.p1.x; x2 = x + scaled_glyph->bbox.p2.x; if (round_glyph_positions == CAIRO_ROUND_GLYPH_POS_ON) y = _cairo_fixed_from_double (_cairo_lround (glyphs[i].y)); else y = _cairo_fixed_from_double (glyphs[i].y); y1 = y + scaled_glyph->bbox.p1.y; y2 = y + scaled_glyph->bbox.p2.y; if (overlap == FALSE) overlap = _range_contains_glyph (&box, x1, y1, x2, y2); if (x1 < box.p1.x) box.p1.x = x1; if (x2 > box.p2.x) box.p2.x = x2; if (y1 < box.p1.y) box.p1.y = y1; if (y2 > box.p2.y) box.p2.y = y2; } _cairo_scaled_font_thaw_cache (scaled_font); if (unlikely (status)) return _cairo_scaled_font_set_error (scaled_font, status); if (box.p1.x < box.p2.x) { _cairo_box_round_to_rectangle (&box, extents); } else { extents->x = extents->y = 0; extents->width = extents->height = 0; } if (overlap_out != NULL) *overlap_out = overlap; return CAIRO_STATUS_SUCCESS; } void _cairo_scaled_font_glyph_approximate_extents (cairo_scaled_font_t *scaled_font, const cairo_glyph_t *glyphs, int num_glyphs, cairo_rectangle_int_t *extents) { double x0 = HUGE_VAL, x1 = -HUGE_VAL; double y0 = HUGE_VAL, y1 = -HUGE_VAL; int i; for (i = 0; i < num_glyphs; i++) { double g; g = glyphs[i].x; if (g < x0) x0 = g; if (g > x1) x1 = g; g = glyphs[i].y; if (g < y0) y0 = g; if (g > y1) y1 = g; } if (x0 <= x1 && y0 <= y1) { extents->x = floor (x0 - scaled_font->extents.max_x_advance); extents->width = ceil (x1 + scaled_font->extents.max_x_advance); extents->width -= extents->x; extents->y = floor (y0 - scaled_font->extents.ascent); extents->height = ceil (y1 + scaled_font->extents.descent); extents->height -= extents->y; } else { extents->x = extents->y = 0; extents->width = extents->height = 0; } } cairo_status_t _cairo_scaled_font_show_glyphs (cairo_scaled_font_t *scaled_font, cairo_operator_t op, const cairo_pattern_t *pattern, cairo_surface_t *surface, int source_x, int source_y, int dest_x, int dest_y, unsigned int width, unsigned int height, cairo_glyph_t *glyphs, int num_glyphs, cairo_region_t *clip_region) { cairo_status_t status; cairo_surface_t *mask = NULL; cairo_format_t mask_format = CAIRO_FORMAT_A1; /* shut gcc up */ cairo_surface_pattern_t mask_pattern; int i; /* These operators aren't interpreted the same way by the backends; * they are implemented in terms of other operators in cairo-gstate.c */ assert (op != CAIRO_OPERATOR_SOURCE && op != CAIRO_OPERATOR_CLEAR); if (scaled_font->status) return scaled_font->status; if (!num_glyphs) return CAIRO_STATUS_SUCCESS; if (scaled_font->backend->show_glyphs != NULL) { int remaining_glyphs = num_glyphs; status = scaled_font->backend->show_glyphs (scaled_font, op, pattern, surface, source_x, source_y, dest_x, dest_y, width, height, glyphs, num_glyphs, clip_region, &remaining_glyphs); glyphs += num_glyphs - remaining_glyphs; num_glyphs = remaining_glyphs; if (remaining_glyphs == 0) status = CAIRO_STATUS_SUCCESS; if (status != CAIRO_INT_STATUS_UNSUPPORTED) return _cairo_scaled_font_set_error (scaled_font, status); } /* Font display routine either does not exist or failed. */ _cairo_scaled_font_freeze_cache (scaled_font); for (i = 0; i < num_glyphs; i++) { int x, y; cairo_image_surface_t *glyph_surface; cairo_scaled_glyph_t *scaled_glyph; status = _cairo_scaled_glyph_lookup (scaled_font, glyphs[i].index, CAIRO_SCALED_GLYPH_INFO_SURFACE, &scaled_glyph); if (unlikely (status)) goto CLEANUP_MASK; glyph_surface = scaled_glyph->surface; /* To start, create the mask using the format from the first * glyph. Later we'll deal with different formats. */ if (mask == NULL) { mask_format = glyph_surface->format; mask = cairo_image_surface_create (mask_format, width, height); status = mask->status; if (unlikely (status)) goto CLEANUP_MASK; } /* If we have glyphs of different formats, we "upgrade" the mask * to the wider of the formats. */ if (glyph_surface->format != mask_format && _cairo_format_bits_per_pixel (mask_format) < _cairo_format_bits_per_pixel (glyph_surface->format) ) { cairo_surface_t *new_mask; switch (glyph_surface->format) { case CAIRO_FORMAT_ARGB32: case CAIRO_FORMAT_A8: case CAIRO_FORMAT_A1: mask_format = glyph_surface->format; break; case CAIRO_FORMAT_RGB16_565: case CAIRO_FORMAT_RGB24: case CAIRO_FORMAT_INVALID: default: ASSERT_NOT_REACHED; mask_format = CAIRO_FORMAT_ARGB32; break; } new_mask = cairo_image_surface_create (mask_format, width, height); status = new_mask->status; if (unlikely (status)) { cairo_surface_destroy (new_mask); goto CLEANUP_MASK; } _cairo_pattern_init_for_surface (&mask_pattern, mask); /* Note that we only upgrade masks, i.e. A1 -> A8 -> ARGB32, so there is * never any component alpha here. */ status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, &_cairo_pattern_white.base, &mask_pattern.base, new_mask, 0, 0, 0, 0, 0, 0, width, height, NULL); _cairo_pattern_fini (&mask_pattern.base); if (unlikely (status)) { cairo_surface_destroy (new_mask); goto CLEANUP_MASK; } cairo_surface_destroy (mask); mask = new_mask; } if (glyph_surface->width && glyph_surface->height) { cairo_surface_pattern_t glyph_pattern; /* round glyph locations to the nearest pixel */ /* XXX: FRAGILE: We're ignoring device_transform scaling here. A bug? */ x = _cairo_lround (glyphs[i].x - glyph_surface->base.device_transform.x0); y = _cairo_lround (glyphs[i].y - glyph_surface->base.device_transform.y0); _cairo_pattern_init_for_surface (&glyph_pattern, &glyph_surface->base); if (mask_format == CAIRO_FORMAT_ARGB32) glyph_pattern.base.has_component_alpha = TRUE; status = _cairo_surface_composite (CAIRO_OPERATOR_ADD, &_cairo_pattern_white.base, &glyph_pattern.base, mask, 0, 0, 0, 0, x - dest_x, y - dest_y, glyph_surface->width, glyph_surface->height, NULL); _cairo_pattern_fini (&glyph_pattern.base); if (unlikely (status)) goto CLEANUP_MASK; } } _cairo_pattern_init_for_surface (&mask_pattern, mask); if (mask_format == CAIRO_FORMAT_ARGB32) mask_pattern.base.has_component_alpha = TRUE; status = _cairo_surface_composite (op, pattern, &mask_pattern.base, surface, source_x, source_y, 0, 0, dest_x, dest_y, width, height, clip_region); _cairo_pattern_fini (&mask_pattern.base); CLEANUP_MASK: _cairo_scaled_font_thaw_cache (scaled_font); if (mask != NULL) cairo_surface_destroy (mask); return _cairo_scaled_font_set_error (scaled_font, status); } /* Add a single-device-unit rectangle to a path. */ static cairo_status_t _add_unit_rectangle_to_path (cairo_path_fixed_t *path, cairo_fixed_t x, cairo_fixed_t y) { cairo_status_t status; status = _cairo_path_fixed_move_to (path, x, y); if (unlikely (status)) return status; status = _cairo_path_fixed_rel_line_to (path, _cairo_fixed_from_int (1), _cairo_fixed_from_int (0)); if (unlikely (status)) return status; status = _cairo_path_fixed_rel_line_to (path, _cairo_fixed_from_int (0), _cairo_fixed_from_int (1)); if (unlikely (status)) return status; status = _cairo_path_fixed_rel_line_to (path, _cairo_fixed_from_int (-1), _cairo_fixed_from_int (0)); if (unlikely (status)) return status; return _cairo_path_fixed_close_path (path); } /** * _trace_mask_to_path: * @bitmap: An alpha mask (either %CAIRO_FORMAT_A1 or %CAIRO_FORMAT_A8) * @path: An initialized path to hold the result * * Given a mask surface, (an alpha image), fill out the provided path * so that when filled it would result in something that approximates * the mask. * * Note: The current tracing code here is extremely primitive. It * operates only on an A1 surface, (converting an A8 surface to A1 if * necessary), and performs the tracing by drawing a little square * around each pixel that is on in the mask. We do not pretend that * this is a high-quality result. But we are leaving it up to someone * who cares enough about getting a better result to implement * something more sophisticated. **/ static cairo_status_t _trace_mask_to_path (cairo_image_surface_t *mask, cairo_path_fixed_t *path, double tx, double ty) { const uint8_t *row; int rows, cols, bytes_per_row; int x, y, bit; double xoff, yoff; cairo_fixed_t x0, y0; cairo_fixed_t px, py; cairo_status_t status; mask = _cairo_image_surface_coerce_to_format (mask, CAIRO_FORMAT_A1); status = mask->base.status; if (unlikely (status)) return status; cairo_surface_get_device_offset (&mask->base, &xoff, &yoff); x0 = _cairo_fixed_from_double (tx - xoff); y0 = _cairo_fixed_from_double (ty - yoff); bytes_per_row = (mask->width + 7) / 8; row = mask->data; for (y = 0, rows = mask->height; rows--; row += mask->stride, y++) { const uint8_t *byte_ptr = row; x = 0; py = _cairo_fixed_from_int (y); for (cols = bytes_per_row; cols--; ) { uint8_t byte = *byte_ptr++; if (byte == 0) { x += 8; continue; } byte = CAIRO_BITSWAP8_IF_LITTLE_ENDIAN (byte); for (bit = 1 << 7; bit && x < mask->width; bit >>= 1, x++) { if (byte & bit) { px = _cairo_fixed_from_int (x); status = _add_unit_rectangle_to_path (path, px + x0, py + y0); if (unlikely (status)) goto BAIL; } } } } BAIL: cairo_surface_destroy (&mask->base); return status; } cairo_status_t _cairo_scaled_font_glyph_path (cairo_scaled_font_t *scaled_font, const cairo_glyph_t *glyphs, int num_glyphs, cairo_path_fixed_t *path) { cairo_status_t status; int i; status = scaled_font->status; if (unlikely (status)) return status; _cairo_scaled_font_freeze_cache (scaled_font); for (i = 0; i < num_glyphs; i++) { cairo_scaled_glyph_t *scaled_glyph; status = _cairo_scaled_glyph_lookup (scaled_font, glyphs[i].index, CAIRO_SCALED_GLYPH_INFO_PATH, &scaled_glyph); if (status == CAIRO_STATUS_SUCCESS) { status = _cairo_path_fixed_append (path, scaled_glyph->path, _cairo_fixed_from_double (glyphs[i].x), _cairo_fixed_from_double (glyphs[i].y)); } else if (status == CAIRO_INT_STATUS_UNSUPPORTED) { /* If the font is incapable of providing a path, then we'll * have to trace our own from a surface. */ status = _cairo_scaled_glyph_lookup (scaled_font, glyphs[i].index, CAIRO_SCALED_GLYPH_INFO_SURFACE, &scaled_glyph); if (unlikely (status)) goto BAIL; status = _trace_mask_to_path (scaled_glyph->surface, path, glyphs[i].x, glyphs[i].y); } if (unlikely (status)) goto BAIL; } BAIL: _cairo_scaled_font_thaw_cache (scaled_font); return _cairo_scaled_font_set_error (scaled_font, status); } /** * _cairo_scaled_glyph_set_metrics: * @scaled_glyph: a #cairo_scaled_glyph_t * @scaled_font: a #cairo_scaled_font_t * @fs_metrics: a #cairo_text_extents_t in font space * * _cairo_scaled_glyph_set_metrics() stores user space metrics * for the specified glyph given font space metrics. It is * called by the font backend when initializing a glyph with * %CAIRO_SCALED_GLYPH_INFO_METRICS. **/ void _cairo_scaled_glyph_set_metrics (cairo_scaled_glyph_t *scaled_glyph, cairo_scaled_font_t *scaled_font, cairo_text_extents_t *fs_metrics) { cairo_bool_t first = TRUE; double hm, wm; double min_user_x = 0.0, max_user_x = 0.0, min_user_y = 0.0, max_user_y = 0.0; double min_device_x = 0.0, max_device_x = 0.0, min_device_y = 0.0, max_device_y = 0.0; double device_x_advance, device_y_advance; scaled_glyph->fs_metrics = *fs_metrics; for (hm = 0.0; hm <= 1.0; hm += 1.0) for (wm = 0.0; wm <= 1.0; wm += 1.0) { double x, y; /* Transform this corner to user space */ x = fs_metrics->x_bearing + fs_metrics->width * wm; y = fs_metrics->y_bearing + fs_metrics->height * hm; cairo_matrix_transform_point (&scaled_font->font_matrix, &x, &y); if (first) { min_user_x = max_user_x = x; min_user_y = max_user_y = y; } else { if (x < min_user_x) min_user_x = x; if (x > max_user_x) max_user_x = x; if (y < min_user_y) min_user_y = y; if (y > max_user_y) max_user_y = y; } /* Transform this corner to device space from glyph origin */ x = fs_metrics->x_bearing + fs_metrics->width * wm; y = fs_metrics->y_bearing + fs_metrics->height * hm; cairo_matrix_transform_distance (&scaled_font->scale, &x, &y); if (first) { min_device_x = max_device_x = x; min_device_y = max_device_y = y; } else { if (x < min_device_x) min_device_x = x; if (x > max_device_x) max_device_x = x; if (y < min_device_y) min_device_y = y; if (y > max_device_y) max_device_y = y; } first = FALSE; } scaled_glyph->metrics.x_bearing = min_user_x; scaled_glyph->metrics.y_bearing = min_user_y; scaled_glyph->metrics.width = max_user_x - min_user_x; scaled_glyph->metrics.height = max_user_y - min_user_y; scaled_glyph->metrics.x_advance = fs_metrics->x_advance; scaled_glyph->metrics.y_advance = fs_metrics->y_advance; cairo_matrix_transform_distance (&scaled_font->font_matrix, &scaled_glyph->metrics.x_advance, &scaled_glyph->metrics.y_advance); device_x_advance = fs_metrics->x_advance; device_y_advance = fs_metrics->y_advance; cairo_matrix_transform_distance (&scaled_font->scale, &device_x_advance, &device_y_advance); scaled_glyph->bbox.p1.x = _cairo_fixed_from_double (min_device_x); scaled_glyph->bbox.p1.y = _cairo_fixed_from_double (min_device_y); scaled_glyph->bbox.p2.x = _cairo_fixed_from_double (max_device_x); scaled_glyph->bbox.p2.y = _cairo_fixed_from_double (max_device_y); scaled_glyph->x_advance = _cairo_lround (device_x_advance); scaled_glyph->y_advance = _cairo_lround (device_y_advance); scaled_glyph->has_info |= CAIRO_SCALED_GLYPH_INFO_METRICS; } void _cairo_scaled_glyph_set_surface (cairo_scaled_glyph_t *scaled_glyph, cairo_scaled_font_t *scaled_font, cairo_image_surface_t *surface) { if (scaled_glyph->surface != NULL) cairo_surface_destroy (&scaled_glyph->surface->base); /* sanity check the backend glyph contents */ _cairo_debug_check_image_surface_is_defined (&surface->base); scaled_glyph->surface = surface; if (surface != NULL) scaled_glyph->has_info |= CAIRO_SCALED_GLYPH_INFO_SURFACE; else scaled_glyph->has_info &= ~CAIRO_SCALED_GLYPH_INFO_SURFACE; } void _cairo_scaled_glyph_set_path (cairo_scaled_glyph_t *scaled_glyph, cairo_scaled_font_t *scaled_font, cairo_path_fixed_t *path) { if (scaled_glyph->path != NULL) _cairo_path_fixed_destroy (scaled_glyph->path); scaled_glyph->path = path; if (path != NULL) scaled_glyph->has_info |= CAIRO_SCALED_GLYPH_INFO_PATH; else scaled_glyph->has_info &= ~CAIRO_SCALED_GLYPH_INFO_PATH; } void _cairo_scaled_glyph_set_recording_surface (cairo_scaled_glyph_t *scaled_glyph, cairo_scaled_font_t *scaled_font, cairo_surface_t *recording_surface) { if (scaled_glyph->recording_surface != NULL) { cairo_surface_finish (scaled_glyph->recording_surface); cairo_surface_destroy (scaled_glyph->recording_surface); } scaled_glyph->recording_surface = recording_surface; if (recording_surface != NULL) scaled_glyph->has_info |= CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE; else scaled_glyph->has_info &= ~CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE; } static cairo_bool_t _cairo_scaled_glyph_page_can_remove (const void *closure) { const cairo_scaled_glyph_page_t *page = closure; const cairo_scaled_font_t *scaled_font; scaled_font = (cairo_scaled_font_t *) page->cache_entry.hash; return scaled_font->cache_frozen == 0; } static cairo_status_t _cairo_scaled_font_allocate_glyph (cairo_scaled_font_t *scaled_font, cairo_scaled_glyph_t **scaled_glyph) { cairo_scaled_glyph_page_t *page; cairo_status_t status; /* only the first page in the list may contain available slots */ if (! cairo_list_is_empty (&scaled_font->glyph_pages)) { page = cairo_list_last_entry (&scaled_font->glyph_pages, cairo_scaled_glyph_page_t, link); if (page->num_glyphs < CAIRO_SCALED_GLYPH_PAGE_SIZE) { *scaled_glyph = &page->glyphs[page->num_glyphs++]; return CAIRO_STATUS_SUCCESS; } } page = malloc (sizeof (cairo_scaled_glyph_page_t)); if (unlikely (page == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); page->cache_entry.hash = (unsigned long) scaled_font; page->cache_entry.size = 1; /* XXX occupancy weighting? */ page->num_glyphs = 0; CAIRO_MUTEX_LOCK (_cairo_scaled_glyph_page_cache_mutex); if (scaled_font->global_cache_frozen == FALSE) { if (unlikely (cairo_scaled_glyph_page_cache.hash_table == NULL)) { status = _cairo_cache_init (&cairo_scaled_glyph_page_cache, NULL, _cairo_scaled_glyph_page_can_remove, _cairo_scaled_glyph_page_destroy, MAX_GLYPH_PAGES_CACHED); if (unlikely (status)) { CAIRO_MUTEX_UNLOCK (_cairo_scaled_glyph_page_cache_mutex); free (page); return status; } } _cairo_cache_freeze (&cairo_scaled_glyph_page_cache); scaled_font->global_cache_frozen = TRUE; } status = _cairo_cache_insert (&cairo_scaled_glyph_page_cache, &page->cache_entry); CAIRO_MUTEX_UNLOCK (_cairo_scaled_glyph_page_cache_mutex); if (unlikely (status)) { free (page); return status; } cairo_list_add_tail (&page->link, &scaled_font->glyph_pages); *scaled_glyph = &page->glyphs[page->num_glyphs++]; return CAIRO_STATUS_SUCCESS; } static void _cairo_scaled_font_free_last_glyph (cairo_scaled_font_t *scaled_font, cairo_scaled_glyph_t *scaled_glyph) { cairo_scaled_glyph_page_t *page; assert (! cairo_list_is_empty (&scaled_font->glyph_pages)); page = cairo_list_last_entry (&scaled_font->glyph_pages, cairo_scaled_glyph_page_t, link); assert (scaled_glyph == &page->glyphs[page->num_glyphs-1]); _cairo_scaled_glyph_fini (scaled_font, scaled_glyph); if (--page->num_glyphs == 0) { _cairo_cache_remove (&cairo_scaled_glyph_page_cache, &page->cache_entry); } } /** * _cairo_scaled_glyph_lookup: * @scaled_font: a #cairo_scaled_font_t * @index: the glyph to create * @info: a #cairo_scaled_glyph_info_t marking which portions of * the glyph should be filled in. * @scaled_glyph_ret: a #cairo_scaled_glyph_t where the glyph * is returned. * * If the desired info is not available, (for example, when trying to * get INFO_PATH with a bitmapped font), this function will return * %CAIRO_INT_STATUS_UNSUPPORTED. * * Note: This function must be called with the scaled font frozen, and it must * remain frozen for as long as the @scaled_glyph_ret is alive. (If the scaled * font was not frozen, then there is no guarantee that the glyph would not be * evicted before you tried to access it.) See * _cairo_scaled_font_freeze_cache() and _cairo_scaled_font_thaw_cache(). * * Returns: a glyph with the requested portions filled in. Glyph * lookup is cached and glyph will be automatically freed along * with the scaled_font so no explicit free is required. * @info can be one or more of: * %CAIRO_SCALED_GLYPH_INFO_METRICS - glyph metrics and bounding box * %CAIRO_SCALED_GLYPH_INFO_SURFACE - surface holding glyph image * %CAIRO_SCALED_GLYPH_INFO_PATH - path holding glyph outline in device space **/ cairo_int_status_t _cairo_scaled_glyph_lookup (cairo_scaled_font_t *scaled_font, unsigned long index, cairo_scaled_glyph_info_t info, cairo_scaled_glyph_t **scaled_glyph_ret) { cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_scaled_glyph_t *scaled_glyph; cairo_scaled_glyph_info_t need_info; *scaled_glyph_ret = NULL; if (unlikely (scaled_font->status)) return scaled_font->status; assert (CAIRO_MUTEX_IS_LOCKED(scaled_font->mutex)); if (CAIRO_INJECT_FAULT ()) return _cairo_error (CAIRO_STATUS_NO_MEMORY); /* * Check cache for glyph */ scaled_glyph = _cairo_hash_table_lookup (scaled_font->glyphs, (cairo_hash_entry_t *) &index); if (scaled_glyph == NULL) { status = _cairo_scaled_font_allocate_glyph (scaled_font, &scaled_glyph); if (unlikely (status)) goto err; memset (scaled_glyph, 0, sizeof (cairo_scaled_glyph_t)); _cairo_scaled_glyph_set_index (scaled_glyph, index); /* ask backend to initialize metrics and shape fields */ status = scaled_font->backend->scaled_glyph_init (scaled_font, scaled_glyph, info | CAIRO_SCALED_GLYPH_INFO_METRICS); if (unlikely (status)) { _cairo_scaled_font_free_last_glyph (scaled_font, scaled_glyph); goto err; } status = _cairo_hash_table_insert (scaled_font->glyphs, &scaled_glyph->hash_entry); if (unlikely (status)) { _cairo_scaled_font_free_last_glyph (scaled_font, scaled_glyph); goto err; } } /* * Check and see if the glyph, as provided, * already has the requested data and amend it if not */ need_info = info & ~scaled_glyph->has_info; if (need_info) { status = scaled_font->backend->scaled_glyph_init (scaled_font, scaled_glyph, need_info); if (unlikely (status)) goto err; /* Don't trust the scaled_glyph_init() return value, the font * backend may not even know about some of the info. For example, * no backend other than the user-fonts knows about recording-surface * glyph info. */ if (info & ~scaled_glyph->has_info) return CAIRO_INT_STATUS_UNSUPPORTED; } *scaled_glyph_ret = scaled_glyph; return CAIRO_STATUS_SUCCESS; err: /* It's not an error for the backend to not support the info we want. */ if (status != CAIRO_INT_STATUS_UNSUPPORTED) status = _cairo_scaled_font_set_error (scaled_font, status); return status; } double _cairo_scaled_font_get_max_scale (cairo_scaled_font_t *scaled_font) { return scaled_font->max_scale; } /** * cairo_scaled_font_get_font_face: * @scaled_font: a #cairo_scaled_font_t * * Gets the font face that this scaled font uses. This might be the * font face passed to cairo_scaled_font_create(), but this does not * hold true for all possible cases. * * Return value: The #cairo_font_face_t with which @scaled_font was * created. * * Since: 1.2 **/ cairo_font_face_t * cairo_scaled_font_get_font_face (cairo_scaled_font_t *scaled_font) { if (scaled_font->status) return (cairo_font_face_t*) &_cairo_font_face_nil; if (scaled_font->original_font_face != NULL) return scaled_font->original_font_face; return scaled_font->font_face; } slim_hidden_def (cairo_scaled_font_get_font_face); /** * cairo_scaled_font_get_font_matrix: * @scaled_font: a #cairo_scaled_font_t * @font_matrix: return value for the matrix * * Stores the font matrix with which @scaled_font was created into * @matrix. * * Since: 1.2 **/ void cairo_scaled_font_get_font_matrix (cairo_scaled_font_t *scaled_font, cairo_matrix_t *font_matrix) { if (scaled_font->status) { cairo_matrix_init_identity (font_matrix); return; } *font_matrix = scaled_font->font_matrix; } slim_hidden_def (cairo_scaled_font_get_font_matrix); /** * cairo_scaled_font_get_ctm: * @scaled_font: a #cairo_scaled_font_t * @ctm: return value for the CTM * * Stores the CTM with which @scaled_font was created into @ctm. * Note that the translation offsets (x0, y0) of the CTM are ignored * by cairo_scaled_font_create(). So, the matrix this * function returns always has 0,0 as x0,y0. * * Since: 1.2 **/ void cairo_scaled_font_get_ctm (cairo_scaled_font_t *scaled_font, cairo_matrix_t *ctm) { if (scaled_font->status) { cairo_matrix_init_identity (ctm); return; } *ctm = scaled_font->ctm; } slim_hidden_def (cairo_scaled_font_get_ctm); /** * cairo_scaled_font_get_scale_matrix: * @scaled_font: a #cairo_scaled_font_t * @scale_matrix: return value for the matrix * * Stores the scale matrix of @scaled_font into @matrix. * The scale matrix is product of the font matrix and the ctm * associated with the scaled font, and hence is the matrix mapping from * font space to device space. * * Since: 1.8 **/ void cairo_scaled_font_get_scale_matrix (cairo_scaled_font_t *scaled_font, cairo_matrix_t *scale_matrix) { if (scaled_font->status) { cairo_matrix_init_identity (scale_matrix); return; } *scale_matrix = scaled_font->scale; } /** * cairo_scaled_font_get_font_options: * @scaled_font: a #cairo_scaled_font_t * @options: return value for the font options * * Stores the font options with which @scaled_font was created into * @options. * * Since: 1.2 **/ void cairo_scaled_font_get_font_options (cairo_scaled_font_t *scaled_font, cairo_font_options_t *options) { if (cairo_font_options_status (options)) return; if (scaled_font->status) { _cairo_font_options_init_default (options); return; } _cairo_font_options_init_copy (options, &scaled_font->options); } slim_hidden_def (cairo_scaled_font_get_font_options);