efl/legacy/evas/src/lib/engines/common/evas_font_query.c

803 lines
25 KiB
C

#include "evas_common.h"
#include "language/evas_bidi_utils.h" /*defines BIDI_SUPPORT if possible */
#include "evas_font_private.h" /* for Frame-Queuing support */
#include "evas_font_ot.h"
/* FIXME: Check coverage according to the font and not by actually loading */
/**
* @internal
* Find the end of a run according to font coverage, and return the base script
* font and the current wanted font.
*
* @param[in] fn the font to use.
* @param script_fi The base font instance to be used with the script. If NULL, then it's calculated and returned in this variable, if not NULL, it's used and not modified.
* @param[out] cur_fi The font instance found for the current run.
* @param[in] script the base script
* @param[in] text the text to work on.
* @param[in] run_let the current run len, i.e "search limit".
* @return length of the run found.
*/
EAPI int
evas_common_font_query_run_font_end_get(RGBA_Font *fn, RGBA_Font_Int **script_fi, RGBA_Font_Int **cur_fi, Evas_Script_Type script, const Eina_Unicode *text, int run_len)
{
RGBA_Font_Int *fi = NULL;
const Eina_Unicode *run_end = text + run_len;
const Eina_Unicode *itr;
/* If there's no current script_fi, find it first */
if (!*script_fi)
{
const Eina_Unicode *base_char = NULL;
/* Skip common chars */
for (base_char = text ;
(base_char < run_end) &&
(evas_common_language_char_script_get(*base_char) != script) ;
base_char++)
;
if (base_char == run_end) base_char = text;
/* Find the first renderable char */
while (base_char < run_end)
{
/* 0x1F is the last ASCII contral char, just a hack in
* the meanwhile. */
if ((*base_char > 0x1F) &&
evas_common_font_glyph_search(fn, &fi, *base_char))
break;
base_char++;
}
/* If everything else fails, at least try to find a font for the
* replacement char */
if (base_char == run_end)
evas_common_font_glyph_search(fn, &fi, REPLACEMENT_CHAR);
if (!fi)
fi = fn->fonts->data;
*script_fi = fi;
}
else
{
fi = *script_fi;
}
/* Find the longest run of the same font starting from the start position
* and update cur_fi accordingly. */
itr = text;
while (itr < run_end)
{
RGBA_Font_Int *tmp_fi;
/* Itr will end up being the first of the next run */
for ( ; itr < run_end ; itr++)
{
/* 0x1F is the last ASCII contral char, just a hack in
* the meanwhile. */
if (*itr <= 0x1F)
continue;
/* Break if either it's not in the font, or if it is in the
* script's font. */
if (fi == *script_fi)
{
if (!evas_common_get_char_index(fi, *itr))
break;
}
else
{
if (evas_common_get_char_index(*script_fi, *itr))
break;
}
}
/* Abort if we reached the end */
if (itr == run_end)
break;
/* If the script font doesn't fit even one char, find a new font. */
if (itr == text)
{
/* If we can find a font, use it. Otherwise, find the first
* char the run of chars that can't be rendered until the first
* one that can. */
if (evas_common_font_glyph_search(fn, &tmp_fi, *itr))
{
fi = tmp_fi;
}
else
{
itr++;
/* Go through all the chars that can't be rendered with any
* font */
for ( ; itr < run_end ; itr++)
{
tmp_fi = fi;
if (evas_common_get_char_index(fi, *itr) ||
evas_common_font_glyph_search(fn, &tmp_fi, *itr))
{
fi = tmp_fi;
break;
}
}
/* If we found a renderable character and the found font
* can render the replacement char, continue, otherwise
* find a font most suitable for the replacement char and
* break */
if ((itr == run_end) ||
!evas_common_get_char_index(fi, REPLACEMENT_CHAR))
{
evas_common_font_glyph_search(fn, &fi, REPLACEMENT_CHAR);
break;
}
}
itr++;
}
else
{
/* If this char is not renderable by any font, but the replacement
* char can be rendered using the currentfont, continue this
* run. */
if (!evas_common_font_glyph_search(fn, &tmp_fi, *itr) &&
evas_common_get_char_index(fi, REPLACEMENT_CHAR))
{
itr++;
}
else
{
/* Done, we did as much as possible */
break;
}
}
}
if (fi)
*cur_fi = fi;
else
*cur_fi = *script_fi;
return itr - text;
}
/**
* @internal
* Calculate the kerning between "left" and "right.
*
* @param fi the font instance to use
* @param left the left glyph index
* @param right the right glyph index
* @param[out] kerning the kerning calculated.
* @return FALSE on error, TRUE on success.
*/
EAPI int
evas_common_font_query_kerning(RGBA_Font_Int *fi, FT_UInt left, FT_UInt right,
int *kerning)
{
int *result;
FT_Vector delta;
int key[2];
int error = 1;
key[0] = left;
key[1] = right;
result = eina_hash_find(fi->kerning, key);
if (result)
{
*kerning = result[2];
goto on_correct;
}
/* NOTE: ft2 seems to have a bug. and sometimes returns bizarre
* values to kern by - given same font, same size and same
* prev_index and index. auto/bytecode or none hinting doesn't
* matter */
evas_common_font_int_reload(fi);
FTLOCK();
if (FT_Get_Kerning(fi->src->ft.face,
key[0], key[1],
FT_KERNING_DEFAULT, &delta) == 0)
{
int *push;
FTUNLOCK();
*kerning = delta.x;
push = malloc(sizeof (int) * 3);
if (!push) return 1;
push[0] = key[0];
push[1] = key[1];
push[2] = *kerning;
eina_hash_direct_add(fi->kerning, push, push);
goto on_correct;
}
FTUNLOCK();
error = 0;
on_correct:
return error;
}
/**
* @internal
* Calculate the inset of the text. Inset is the difference between the pen
* position of the first char in the string, and the first pixel drawn.
* (can be negative).
*
* @param fn the font set to use.
* @param text_props the string object.
* @return the calculated inset.
*/
EAPI int
evas_common_font_query_inset(RGBA_Font *fn __UNUSED__, const Evas_Text_Props *text_props)
{
if (!text_props->len) return 0;
return text_props->info->glyph[text_props->start].x_bear;
}
/**
* @internal
* Calculate the right inset of the text. This is the difference between the
* pen position of the glyph after the last glyph in the text, and the last
* pixel drawn in the text (essentially "advance - width" of the last char).
*
* @param fn the font set to use.
* @param text_props the string object.
* @return the calculated inset.
*
* @see evas_common_font_query_inset()
*/
EAPI int
evas_common_font_query_right_inset(RGBA_Font *fn __UNUSED__, const Evas_Text_Props *text_props)
{
const Evas_Font_Glyph_Info *gli;
if (!text_props->len) return 0;
gli = text_props->info->glyph + text_props->start + text_props->len - 1;
/* If the last char is a whitespace, we use the advance as the size,
* so the right_inset is 0. */
if (gli->width == 0)
return 0;
return ((gli > text_props->info->glyph) ?
gli->pen_after - (gli - 1)->pen_after : gli->pen_after) -
(gli->width + gli->x_bear
#ifdef OT_SUPPORT
+ EVAS_FONT_ROUND_26_6_TO_INT(EVAS_FONT_OT_X_OFF_GET(
text_props->info->ot[text_props->start + text_props->len - 1]))
#endif
);
}
/**
* @internal
* Calculate the size of the string (width and height).
* The width is the disntance between the first pen position and the last pixel
* drawn.
* The height is the max ascent+descent of the font.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param[out] w the calculated width
* @param[out] h the calculated height
*/
EAPI void
evas_common_font_query_size(RGBA_Font *fn, const Evas_Text_Props *text_props, int *w, int *h)
{
Evas_Coord ret_w = 0;
if (text_props->len > 0)
{
const Evas_Font_Glyph_Info *glyph = text_props->info->glyph +
text_props->start;
const Evas_Font_Glyph_Info *last_glyph = glyph;
if (text_props->len > 1)
{
last_glyph += text_props->len - 1;
ret_w = last_glyph[-1].pen_after;
if (text_props->start > 0)
ret_w -= glyph[-1].pen_after;
}
#ifdef OT_SUPPORT
ret_w += EVAS_FONT_ROUND_26_6_TO_INT(EVAS_FONT_OT_X_OFF_GET(
text_props->info->ot[text_props->start + text_props->len - 1]));
#endif
ret_w += last_glyph->width + last_glyph->x_bear;
}
if (w) *w = ret_w;
if (h) *h = evas_common_font_max_ascent_get(fn) + evas_common_font_max_descent_get(fn);
}
/**
* @internal
* Calculate the advance of the string. Advance is the distance between the
* first pen position and the pen position after the string.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param[out] h_adv the calculated horizontal advance.
* @param[out] v_adv the calculated vertical advance.
*/
EAPI void
evas_common_font_query_advance(RGBA_Font *fn, const Evas_Text_Props *text_props, int *h_adv, int *v_adv)
{
Evas_Coord ret_adv = 0;
if (text_props->len > 0)
{
// RGBA_Font_Int *fi = text_props->font_instance;
const Evas_Font_Glyph_Info *glyph = text_props->info->glyph +
text_props->start;
const Evas_Font_Glyph_Info *last_glyph = glyph + text_props->len - 1;
ret_adv = last_glyph->pen_after;
if (text_props->start > 0)
ret_adv -= glyph[-1].pen_after;
#if 0
/* Runtime slant adjustment. */
if (fi->runtime_rend & FONT_REND_SLANT)
{
RGBA_Font_Glyph *fg =
evas_common_font_int_cache_glyph_get(fi, last_glyph->index);
if (!fg->glyph_out) evas_common_font_int_cache_glyph_render(fg);
ret_adv += fg->glyph_out->bitmap.rows * _EVAS_FONT_SLANT_TAN;
}
#endif
}
if (h_adv) *h_adv = ret_adv;
if (v_adv) *v_adv = evas_common_font_get_line_advance(fn);
}
/**
* @internal
* Query the coordinates of the char at position pos. If the position is at the
* end of the string (i.e where the finishing null would be) it returns the
* coordinates of the position right after the last char. This is either on
* the left or on the right of the string, depending on BiDi direction. Returned
* width in this case is 0. It returns the x of the leftmost pixel drawn.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param pos the position of the char in the string object (not actual position in the string object, but the position of the source character).
* @param[out] cx the calculated x - CAN BE NULL
* @param[out] cy the calculated y - CAN BE NULL
* @param[out] cw the calculated width - CAN BE NULL
* @param[out] ch the calculated height - CAN BE NULL
* @return TRUE on success, FALSE otherwise.
*
* @see evas_common_font_query_pen_coords()
*/
EAPI int
evas_common_font_query_char_coords(RGBA_Font *fn, const Evas_Text_Props *text_props, int pos, int *cx, int *cy, int *cw, int *ch)
{
int asc, desc;
size_t position = 0;
int ret_val = 0;
EVAS_FONT_WALK_TEXT_INIT();
asc = evas_common_font_max_ascent_get(fn);
desc = evas_common_font_max_descent_get(fn);
position = pos;
/* If it's the null, choose location according to the direction. */
if (position == text_props->text_len)
{
/* if it's rtl then the location is the left of the string,
* otherwise, the right. */
#ifdef BIDI_SUPPORT
if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
{
if (cx) *cx = 0;
if (ch) *ch = asc + desc;
}
else
#endif
{
evas_common_font_query_advance(fn, text_props, cx, ch);
}
if (cy) *cy = 0;
if (cw) *cw = 0;
ret_val = 1;
goto end;
}
Evas_Coord cluster_start = 0, last_end = 0;
int prev_cluster = -1;
int found = 0, items = 1, item_pos = 1;
int last_is_visible = 0;
EVAS_FONT_WALK_TEXT_START()
{
EVAS_FONT_WALK_TEXT_WORK();
if (prev_cluster != (int) EVAS_FONT_WALK_POS)
{
if (found)
{
break;
}
else
{
cluster_start = EVAS_FONT_WALK_PEN_X +
EVAS_FONT_WALK_X_OFF +
EVAS_FONT_WALK_X_BEAR;
}
}
last_is_visible = EVAS_FONT_WALK_IS_VISIBLE;
last_end = EVAS_FONT_WALK_PEN_X + EVAS_FONT_WALK_X_OFF +
EVAS_FONT_WALK_X_BEAR + EVAS_FONT_WALK_WIDTH;
/* we need to see if the char at the visual position is the char wanted */
if ((text_props->bidi.dir == EVAS_BIDI_DIRECTION_LTR) &&
(EVAS_FONT_WALK_POS <= (size_t) position) &&
((((size_t) position) < EVAS_FONT_WALK_POS_NEXT) ||
(EVAS_FONT_WALK_IS_LAST)))
{
found = 1;
#ifdef OT_SUPPORT
items = evas_common_font_ot_cluster_size_get(text_props,
char_index);
#endif
item_pos = position - EVAS_FONT_WALK_POS + 1;
}
else if ((text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL) &&
((EVAS_FONT_WALK_POS_PREV > (size_t) position) ||
(EVAS_FONT_WALK_IS_FIRST)) &&
(((size_t) position) >= EVAS_FONT_WALK_POS))
{
found = 1;
#ifdef OT_SUPPORT
items = evas_common_font_ot_cluster_size_get(text_props,
char_index);
#endif
item_pos = items - (position - EVAS_FONT_WALK_POS);
}
prev_cluster = EVAS_FONT_WALK_POS;
}
EVAS_FONT_WALK_TEXT_END();
if (found)
{
Evas_Coord cluster_w;
cluster_w = last_end - cluster_start;
if (cy) *cy = -asc;
if (ch) *ch = asc + desc;
if (last_is_visible)
{
if (cx) *cx = cluster_start +
(cluster_w / items) *
(item_pos - 1);
if (cw) *cw = (cluster_w / items);
}
else
{
if (cx) *cx = cluster_start;
if (cw) *cw = 0;
}
ret_val = 1;
goto end;
}
end:
return ret_val;
}
/**
* @internal
* Query the coordinates of the char at position pos. If the position is at the
* end of the string (i.e where the finishing null would be) it returns the
* coordinates of the position right after the last char. This is either on
* the left or on the right of the string, depending on BiDi direction. Returned
* advance in this case is 0.
*
* This is the same as evas_common_font_query_char_coords() except that the
* advance of the character is returned instead of the width and the pen
* position is returned instead of the actual pixel position.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param pos the position of the char in the string object (not actual position in the string object, but the position of the source character).
* @param[out] cpenx the calculated x - CAN BE NULL
* @param[out] cy the calculated y - CAN BE NULL
* @param[out] cadv the calculated advance - CAN BE NULL
* @param[out] ch the calculated height - CAN BE NULL
* @return TRUE on success, FALSE otherwise.
*
* @see evas_common_font_query_char_coords()
*/
EAPI int
evas_common_font_query_pen_coords(RGBA_Font *fn, const Evas_Text_Props *text_props, int pos, int *cpen_x, int *cy, int *cadv, int *ch)
{
int asc, desc;
size_t position;
int ret_val = 0;
EVAS_FONT_WALK_TEXT_INIT();
asc = evas_common_font_max_ascent_get(fn);
desc = evas_common_font_max_descent_get(fn);
position = pos;
/* If it's the null, choose location according to the direction. */
if (position == text_props->text_len)
{
/* if it's rtl then the location is the left of the string,
* otherwise, the right. */
#ifdef BIDI_SUPPORT
if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
{
if (cpen_x) *cpen_x = 0;
if (ch) *ch = asc + desc;
}
else
#endif
{
evas_common_font_query_advance(fn, text_props, cpen_x, ch);
}
if (cy) *cy = 0;
if (cadv) *cadv = 0;
ret_val = 1;
goto end;
}
Evas_Coord cluster_start = 0;
int prev_cluster = -1;
int found = 0, items = 1, item_pos = 1;
int last_is_visible = 0;
EVAS_FONT_WALK_TEXT_START()
{
EVAS_FONT_WALK_TEXT_WORK();
if (prev_cluster != (int) EVAS_FONT_WALK_POS)
{
if (found)
{
break;
}
else
{
cluster_start = EVAS_FONT_WALK_PEN_X;
}
}
last_is_visible = EVAS_FONT_WALK_IS_VISIBLE;
if ((text_props->bidi.dir == EVAS_BIDI_DIRECTION_LTR) &&
(EVAS_FONT_WALK_POS <= (size_t) position) &&
((((size_t) position) < EVAS_FONT_WALK_POS_NEXT) ||
(EVAS_FONT_WALK_IS_LAST)))
{
found = 1;
#ifdef OT_SUPPORT
items = evas_common_font_ot_cluster_size_get(text_props,
char_index);
#endif
item_pos = position - EVAS_FONT_WALK_POS + 1;
}
else if ((text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL) &&
((EVAS_FONT_WALK_POS_PREV > (size_t) position) ||
(EVAS_FONT_WALK_IS_FIRST)) &&
(((size_t) position) >= EVAS_FONT_WALK_POS))
{
found = 1;
#ifdef OT_SUPPORT
items = evas_common_font_ot_cluster_size_get(text_props,
char_index);
#endif
item_pos = items - (position - EVAS_FONT_WALK_POS);
}
prev_cluster = EVAS_FONT_WALK_POS;
}
EVAS_FONT_WALK_TEXT_END();
if (found)
{
Evas_Coord cluster_adv;
cluster_adv = EVAS_FONT_WALK_PEN_X - cluster_start;
if (cy) *cy = -asc;
if (ch) *ch = asc + desc;
if (last_is_visible)
{
if (cpen_x) *cpen_x = cluster_start +
(cluster_adv / items) *
(item_pos - 1);
if (cadv) *cadv = (cluster_adv / items);
}
else
{
if (cpen_x) *cpen_x = EVAS_FONT_WALK_PEN_X;
if (cadv) *cadv = 0;
}
ret_val = 1;
goto end;
}
end:
return ret_val;
}
/**
* @internal
* Find the character at a specific x, y coordinates and return it's position
* in the text (not in the text object, but in the source text). Also calculate
* the char's geometry.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param x the x to look at.
* @param y the y to look at.
* @param[out] cx the calculated x - CAN BE NULL
* @param[out] cy the calculated y - CAN BE NULL
* @param[out] cw the calculated width - CAN BE NULL
* @param[out] ch the calculated height - CAN BE NULL
* @return the position found, -1 on failure.
*/
EAPI int
evas_common_font_query_char_at_coords(RGBA_Font *fn, const Evas_Text_Props *text_props, int x, int y, int *cx, int *cy, int *cw, int *ch)
{
int asc, desc;
int ret_val = -1;
EVAS_FONT_WALK_TEXT_INIT();
asc = evas_common_font_max_ascent_get(fn);
desc = evas_common_font_max_descent_get(fn);
Evas_Coord cluster_start = 0;
int prev_cluster = -1;
int found = 0, items = 1;
EVAS_FONT_WALK_TEXT_START()
{
EVAS_FONT_WALK_TEXT_WORK();
if (prev_cluster != (int) EVAS_FONT_WALK_POS)
{
if (found)
{
break;
}
else
{
cluster_start = EVAS_FONT_WALK_PEN_X;
}
}
if (!EVAS_FONT_WALK_IS_VISIBLE) continue;
/* we need to see if the char at the visual position is the char,
* we check that by checking if it's before the current pen
* position and the next */
if ((x >= EVAS_FONT_WALK_PEN_X) &&
(x <= (EVAS_FONT_WALK_PEN_X_AFTER)) && (y >= -asc) && (y <= desc))
{
#ifdef OT_SUPPORT
items = evas_common_font_ot_cluster_size_get(text_props,
char_index);
#endif
found = 1;
}
prev_cluster = EVAS_FONT_WALK_POS;
}
EVAS_FONT_WALK_TEXT_END();
if (found)
{
int item_pos;
Evas_Coord cluster_adv;
cluster_adv = EVAS_FONT_WALK_PEN_X - cluster_start;
if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_LTR)
{
double part;
part = cluster_adv / items;
item_pos = (int) ((x - cluster_start) / part);
}
else
{
double part;
part = cluster_adv / items;
item_pos = items - ((int) ((x - cluster_start) / part)) - 1;
}
if (cx) *cx = EVAS_FONT_WALK_PEN_X +
((cluster_adv / items) * (item_pos - 1));
if (cy) *cy = -asc;
if (cw) *cw = (cluster_adv / items);
if (ch) *ch = asc + desc;
ret_val = prev_cluster + item_pos;
goto end;
}
end:
return ret_val;
}
/**
* @internal
* Find one after the last character that fits until the boundaries set by x
* and y. I.e find the first char that doesn't fit.
* This LOGICALLY walks the string. This is needed for wrapping for example
* where we want the first part to be the first logical part.
*
* @param fn the font set to use.
* @param text_props the string object.
* @param x the x boundary.
* @param y the y boundary.
* @return the position found, -1 on failure.
*/
EAPI int
evas_common_font_query_last_up_to_pos(RGBA_Font *fn, const Evas_Text_Props *text_props, int x, int y)
{
int asc, desc;
int ret=-1;
asc = evas_common_font_max_ascent_get(fn);
desc = evas_common_font_max_descent_get(fn);
#ifdef BIDI_SUPPORT
if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
{
Evas_Font_Glyph_Info *gli = NULL;
Evas_Coord full_adv = 0, pen_x = 0, start_pen = 0;
int i;
if ((text_props->info) && (text_props->len > 0))
{
gli = text_props->info->glyph + text_props->start;
full_adv = gli[text_props->len - 1].pen_after;
if (text_props->start > 0)
{
start_pen = gli[-1].pen_after;
full_adv -= start_pen;
}
gli += text_props->len - 1;
for (i = text_props->len - 1 ; i >= 0 ; i--, gli--)
{
pen_x = full_adv - (gli->pen_after - start_pen);
/* If invisible, skip */
if (gli->index == 0) continue;
if ((x >= pen_x) &&
(((i == 0) && (x <= full_adv)) ||
(x <= (full_adv - (gli[-1].pen_after - start_pen)))) &&
(y >= -asc) && (y <= desc))
{
#ifdef OT_SUPPORT
ret = EVAS_FONT_OT_POS_GET(
text_props->info->ot[text_props->start + i]) -
text_props->text_offset;
#else
ret = text_props->text_len - i - 1;
#endif
goto end;
}
}
}
}
else
#endif
{
EVAS_FONT_WALK_TEXT_INIT();
/* When text is not rtl, visual direction = logical direction */
EVAS_FONT_WALK_TEXT_START()
{
EVAS_FONT_WALK_TEXT_WORK();
if (!EVAS_FONT_WALK_IS_VISIBLE) continue;
if ((x >= EVAS_FONT_WALK_PEN_X) &&
(x <= (EVAS_FONT_WALK_PEN_X_AFTER)) &&
(y >= -asc) && (y <= desc))
{
ret = EVAS_FONT_WALK_POS;
goto end;
}
}
EVAS_FONT_WALK_TEXT_END();
}
end:
return ret;
}