/* Pango
* shape.c: Convert characters into glyphs.
*
* Copyright (C) 1999 Red Hat Software
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:glyphs
* @short_description:Structures for storing information about glyphs
* @title:Glyph Storage
*
* pango_shape() produces a string of glyphs which
* can be measured or drawn to the screen. The following
* structures are used to store information about
* glyphs.
*/
#include "config.h"
#include "pango-impl-utils.h"
#include "pango-glyph.h"
#include "pango-engine-private.h"
#include <string.h>
/**
* pango_shape:
* @text: the text to process
* @length: the length (in bytes) of @text
* @analysis: #PangoAnalysis structure from pango_itemize()
* @glyphs: glyph string in which to store results
*
* Given a segment of text and the corresponding
* #PangoAnalysis structure returned from pango_itemize(),
* convert the characters into glyphs. You may also pass
* in only a substring of the item from pango_itemize().
*
* It is recommended that you use pango_shape_full() instead, since
* that API allows for shaping interaction happening across text item
* boundaries.
*/
void
pango_shape (const gchar *text,
gint length,
const PangoAnalysis *analysis,
PangoGlyphString *glyphs)
{
pango_shape_full (text, length, text, length, analysis, glyphs);
}
/**
* pango_shape_full:
* @item_text: valid UTF-8 text to shape.
* @item_length: the length (in bytes) of @item_text. -1 means nul-terminated text.
* @paragraph_text: (allow-none): text of the paragraph (see details). May be %NULL.
* @paragraph_length: the length (in bytes) of @paragraph_text. -1 means nul-terminated text.
* @analysis: #PangoAnalysis structure from pango_itemize().
* @glyphs: glyph string in which to store results.
*
* Given a segment of text and the corresponding
* #PangoAnalysis structure returned from pango_itemize(),
* convert the characters into glyphs. You may also pass
* in only a substring of the item from pango_itemize().
*
* This is similar to pango_shape(), except it also can optionally take
* the full paragraph text as input, which will then be used to perform
* certain cross-item shaping interactions. If you have access to the broader
* text of which @item_text is part of, provide the broader text as
* @paragraph_text. If @paragraph_text is %NULL, item text is used instead.
*
* Since: 1.32
*/
void
pango_shape_full (const gchar *item_text,
gint item_length,
const gchar *paragraph_text,
gint paragraph_length,
const PangoAnalysis *analysis,
PangoGlyphString *glyphs)
{
int i;
int last_cluster;
glyphs->num_glyphs = 0;
if (item_length == -1)
item_length = strlen (item_text);
if (!paragraph_text)
{
paragraph_text = item_text;
paragraph_length = item_length;
}
if (paragraph_length == -1)
paragraph_length = strlen (paragraph_text);
g_return_if_fail (paragraph_text <= item_text);
g_return_if_fail (paragraph_text + paragraph_length >= item_text + item_length);
if (G_LIKELY (analysis->shape_engine && analysis->font))
{
_pango_engine_shape_shape (analysis->shape_engine, analysis->font,
item_text, item_length,
paragraph_text, paragraph_length,
analysis, glyphs);
if (G_UNLIKELY (glyphs->num_glyphs == 0))
{
/* If a font has been correctly chosen, but no glyphs are output,
* there's probably something wrong with the shaper, or the font.
*
* Trying to be informative, we print out the font description,
* shaper name, and the text, but to not flood the terminal with
* zillions of the message, we set a flag to only err once per
* font/engine pair.
*
* To do the flag fast, we use the engine qname to qflag the font,
* but also the font description to flag the engine. This is
* supposed to be fast to check, but also avoid writing out
* duplicate warnings when a new PangoFont is created.
*/
GType engine_type = G_OBJECT_TYPE (analysis->shape_engine);
GQuark warned_quark = g_type_qname (engine_type);
if (!g_object_get_qdata (G_OBJECT (analysis->font), warned_quark))
{
PangoFontDescription *desc;
char *font_name;
const char *engine_name;
desc = pango_font_describe (analysis->font);
font_name = pango_font_description_to_string (desc);
pango_font_description_free (desc);
if (!g_object_get_data (G_OBJECT (analysis->shape_engine), font_name))
{
engine_name = g_type_name (engine_type);
if (!engine_name)
engine_name = "(unknown)";
g_warning ("shaping failure, expect ugly output. shape-engine='%s', font='%s', text='%.*s'",
engine_name, font_name, item_length, item_text);
g_object_set_data_full (G_OBJECT (analysis->shape_engine), font_name,
GINT_TO_POINTER (1), NULL);
}
g_free (font_name);
g_object_set_qdata_full (G_OBJECT (analysis->font), warned_quark,
GINT_TO_POINTER (1), NULL);
}
}
}
else
glyphs->num_glyphs = 0;
if (G_UNLIKELY (!glyphs->num_glyphs))
{
PangoEngineShape *fallback_engine = _pango_get_fallback_shaper ();
_pango_engine_shape_shape (fallback_engine, analysis->font,
item_text, item_length,
paragraph_text, paragraph_length,
analysis, glyphs);
if (G_UNLIKELY (!glyphs->num_glyphs))
return;
}
/* make sure last_cluster is invalid */
last_cluster = glyphs->log_clusters[0] - 1;
for (i = 0; i < glyphs->num_glyphs; i++)
{
/* Set glyphs[i].attr.is_cluster_start based on log_clusters[] */
if (glyphs->log_clusters[i] != last_cluster)
{
glyphs->glyphs[i].attr.is_cluster_start = TRUE;
last_cluster = glyphs->log_clusters[i];
}
else
glyphs->glyphs[i].attr.is_cluster_start = FALSE;
/* Shift glyph if width is negative, and negate width.
* This is useful for rotated font matrices and shouldn't
* harm in normal cases.
*/
if (glyphs->glyphs[i].geometry.width < 0)
{
glyphs->glyphs[i].geometry.width = -glyphs->glyphs[i].geometry.width;
glyphs->glyphs[i].geometry.x_offset += glyphs->glyphs[i].geometry.width;
}
}
/* Make sure glyphstring direction conforms to analysis->level */
if (G_UNLIKELY ((analysis->level & 1) &&
glyphs->log_clusters[0] < glyphs->log_clusters[glyphs->num_glyphs - 1]))
{
/* Warn once per shaper */
static GQuark warned_quark = 0; /* MT-safe */
if (!warned_quark)
warned_quark = g_quark_from_static_string ("pango-shape-warned");
if (analysis->shape_engine && !g_object_get_qdata (G_OBJECT (analysis->shape_engine), warned_quark))
{
GType engine_type = G_OBJECT_TYPE (analysis->shape_engine);
const char *engine_name = g_type_name (engine_type);
if (!engine_name)
engine_name = "(unknown)";
g_warning ("Expected RTL run but shape-engine='%s' returned LTR. Fixing.", engine_name);
g_object_set_qdata_full (G_OBJECT (analysis->shape_engine), warned_quark,
GINT_TO_POINTER (1), NULL);
}
/* *Fix* it so we don't crash later */
pango_glyph_string_reverse_range (glyphs, 0, glyphs->num_glyphs);
}
}