blob: 5ad211c70c34f4f1a068536cba8b43d1acd31186 [file] [log] [blame]
/*
* Twin - A Tiny Window System
* Copyright © 2004 Keith Packard <keithp@keithp.com>
* All rights reserved.
*
* 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 the Twin Library; see the file COPYING. If not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "twinint.h"
#if 0
#include <stdio.h>
#define F(x) twin_fixed_to_double(x)
#define S(x) twin_sfixed_to_double(x)
#define G(x) ((double) (x))
#define DBGMSG(x) printf x
#else
#define DBGMSG(x)
#endif
#define SNAPI(p) (((p) + 0x8000) & ~0xffff)
#define SNAPH(p) (((p) + 0x4000) & ~0x7fff)
#define FX(g,i) (((g) * (i)->scale.x) >> 6)
#define FY(g,i) (((g) * (i)->scale.y) >> 6)
typedef struct _twin_text_info {
twin_point_t scale;
twin_point_t pen;
twin_point_t margin;
twin_point_t reverse_scale;
twin_bool_t snap;
twin_matrix_t matrix;
twin_matrix_t pen_matrix;
int n_snap_x;
twin_fixed_t snap_x[TWIN_GLYPH_MAX_SNAP_X];
int n_snap_y;
twin_fixed_t snap_y[TWIN_GLYPH_MAX_SNAP_Y];
} twin_text_info_t;
static void _twin_text_compute_info (twin_path_t *path,
twin_font_t *font,
twin_text_info_t *info)
{
twin_spoint_t origin = _twin_path_current_spoint (path);
/*
* Only hint axis aligned text
*/
if ((path->state.font_style & TWIN_TEXT_UNHINTED) == 0 &&
((path->state.matrix.m[0][1] == 0 &&
path->state.matrix.m[1][0] == 0) ||
(path->state.matrix.m[0][0] == 0 &&
path->state.matrix.m[1][1] == 0)))
{
int xi, yi;
twin_fixed_t margin_x;
if (path->state.matrix.m[0][0] != 0)
xi = 0;
else
xi = 1;
yi = 1-xi;
info->matrix.m[xi][0] = TWIN_FIXED_ONE;
info->matrix.m[xi][1] = 0;
info->matrix.m[yi][0] = 0;
info->matrix.m[yi][1] = TWIN_FIXED_ONE;
if (font->type == TWIN_FONT_TYPE_STROKE) {
info->snap = TWIN_TRUE;
info->matrix.m[2][0] = SNAPI(twin_sfixed_to_fixed (origin.x));
info->matrix.m[2][1] = SNAPI(twin_sfixed_to_fixed (origin.y));
} else {
info->snap = TWIN_FALSE;
info->matrix.m[2][0] = twin_sfixed_to_fixed (origin.x);
info->matrix.m[2][1] = twin_sfixed_to_fixed (origin.y);
}
info->scale.x = twin_fixed_mul (path->state.font_size,
path->state.matrix.m[0][xi]);
info->reverse_scale.x = twin_fixed_div (TWIN_FIXED_ONE,
path->state.matrix.m[0][xi]);
if (info->scale.x < 0)
{
info->scale.x = -info->scale.x;
info->reverse_scale.x = -info->reverse_scale.x;
info->matrix.m[0][xi] = -info->matrix.m[0][xi];
info->matrix.m[1][xi] = -info->matrix.m[1][xi];
}
info->scale.y = twin_fixed_mul (path->state.font_size,
path->state.matrix.m[1][yi]);
info->reverse_scale.y = twin_fixed_div (TWIN_FIXED_ONE,
path->state.matrix.m[1][yi]);
if (info->scale.y < 0)
{
info->scale.y = -info->scale.y;
info->reverse_scale.y = -info->reverse_scale.y;
info->matrix.m[0][yi] = -info->matrix.m[0][yi];
info->matrix.m[1][yi] = -info->matrix.m[1][yi];
}
if (font->type == TWIN_FONT_TYPE_STROKE) {
info->pen.x = SNAPH(info->scale.x / 24);
info->pen.y = SNAPH(info->scale.y / 24);
if (info->pen.x < TWIN_FIXED_HALF)
info->pen.x = TWIN_FIXED_HALF;
if (info->pen.y < TWIN_FIXED_HALF)
info->pen.y = TWIN_FIXED_HALF;
} else {
info->pen.x = 0;
info->pen.y = 0;
}
info->margin.x = info->pen.x;
info->margin.y = info->pen.y;
if (font->type == TWIN_FONT_TYPE_STROKE &&
(path->state.font_style & TWIN_TEXT_BOLD))
{
twin_fixed_t pen_x_add;
twin_fixed_t pen_y_add;
pen_x_add = SNAPH(info->pen.x >> 1);
pen_y_add = SNAPH(info->pen.y >> 1);
if (pen_x_add < TWIN_FIXED_HALF)
pen_x_add = TWIN_FIXED_HALF;
if (pen_y_add < TWIN_FIXED_HALF)
pen_y_add = TWIN_FIXED_HALF;
info->pen.x += pen_x_add;
info->pen.y += pen_y_add;
}
DBGMSG (("pen: %9.4f %9.4f\n", F(info->pen.x), F(info->pen.y)));
margin_x = info->snap ? SNAPI(info->margin.x) : info->margin.x;
twin_matrix_translate (&info->matrix,
margin_x + info->pen.x,
-info->pen.y);
info->pen_matrix = info->matrix;
}
else
{
info->snap = TWIN_FALSE;
info->matrix = path->state.matrix;
info->matrix.m[2][0] = twin_sfixed_to_fixed (origin.x);
info->matrix.m[2][1] = twin_sfixed_to_fixed (origin.y);
info->scale.x = path->state.font_size;
info->scale.y = path->state.font_size;
if (font->type != TWIN_FONT_TYPE_STROKE) {
info->pen.x = info->pen.y = 0;
info->margin.x = info->margin.y = 0;
} else {
if (path->state.font_style & TWIN_TEXT_BOLD)
info->pen.x = path->state.font_size / 16;
else
info->pen.x = path->state.font_size / 24;
info->pen.y = info->pen.x;
info->margin.x = path->state.font_size / 24;
info->margin.y = info->margin.x;
}
info->pen_matrix = path->state.matrix;
twin_matrix_translate (&info->matrix,
info->margin.x + info->pen.x, -info->pen.y);
}
info->pen_matrix.m[2][0] = 0; info->pen_matrix.m[2][1] = 0;
twin_matrix_scale (&info->pen_matrix, info->pen.x, info->pen.y);
if (path->state.font_style & TWIN_TEXT_OBLIQUE)
{
twin_matrix_t m;
m.m[0][0] = TWIN_FIXED_ONE; m.m[0][1] = 0;
m.m[1][0] = -TWIN_FIXED_ONE / 4; m.m[1][1] = TWIN_FIXED_ONE;
m.m[2][0] = 0; m.m[2][1] = 0;
twin_matrix_multiply (&info->matrix, &m, &info->matrix);
}
DBGMSG (("%9.4f %9.4f\n", F(info->matrix.m[0][0]), F(info->matrix.m[0][1])));
DBGMSG (("%9.4f %9.4f\n", F(info->matrix.m[1][0]), F(info->matrix.m[1][1])));
DBGMSG (("%9.4f %9.4f\n", F(info->matrix.m[2][0]), F(info->matrix.m[2][1])));
}
static void _twin_text_compute_snap (twin_text_info_t *info,
const signed char *b)
{
int s, n;
const signed char *snap;
snap = twin_glyph_snap_x (b);
n = twin_glyph_n_snap_x (b);
info->n_snap_x = n;
for (s = 0; s < n; s++)
info->snap_x[s] = FX(snap[s], info);
snap = twin_glyph_snap_y (b);
n = twin_glyph_n_snap_y (b);
info->n_snap_y = n;
for (s = 0; s < n; s++)
info->snap_y[s] = FY(snap[s], info);
}
static twin_path_t * _twin_text_compute_pen (twin_text_info_t *info)
{
twin_path_t *pen = twin_path_create ();
twin_path_set_matrix (pen, info->pen_matrix);
twin_path_circle (pen, 0, 0, TWIN_FIXED_ONE);
return pen;
}
static twin_fixed_t _twin_snap (twin_fixed_t v,
twin_fixed_t *snap,
int n)
{
int s;
for (s = 0; s < n - 1; s++)
{
if (snap[s] <= v && v <= snap[s+1])
{
twin_fixed_t before = snap[s];
twin_fixed_t after = snap[s+1];
twin_fixed_t dist = after - before;
twin_fixed_t snap_before = SNAPI(before);
twin_fixed_t snap_after = SNAPI(after);
twin_fixed_t move_before = snap_before - before;
twin_fixed_t move_after = snap_after - after;
twin_fixed_t dist_before = v - before;
twin_fixed_t dist_after = after - v;
twin_fixed_t move = ((int64_t) dist_before * move_after +
(int64_t) dist_after * move_before) / dist;
DBGMSG (("%9.4f <= %9.4f <= %9.4f\n", F(before), F(v), F(after)));
DBGMSG (("before: %9.4f -> %9.4f\n", F(before), F(snap_before)));
DBGMSG (("after: %9.4f -> %9.4f\n", F(after), F(snap_after)));
DBGMSG (("v: %9.4f -> %9.4f\n", F(v), F(v+move)));
v += move;
break;
}
}
return v;
}
static twin_bool_t twin_find_ucs4_page(twin_font_t *font, uint32_t page)
{
int i;
if (font->cur_page && font->cur_page->page == page)
return TWIN_TRUE;
for (i = 0; i < font->n_charmap; i++)
if (font->charmap[i].page == page) {
font->cur_page = &font->charmap[i];
return TWIN_TRUE;
}
font->cur_page = &font->charmap[0];
return TWIN_FALSE;
}
twin_bool_t twin_has_ucs4 (twin_font_t *font, twin_ucs4_t ucs4)
{
return twin_find_ucs4_page(font, twin_ucs_page(ucs4));
}
#define SNAPX(p) _snap (path, p, snap_x, nsnap_x)
#define SNAPY(p) _snap (path, p, snap_y, nsnap_y)
static const signed char * _twin_g_base (twin_font_t *font, twin_ucs4_t ucs4)
{
int idx = twin_ucs_char_in_page(ucs4);
if (!twin_find_ucs4_page(font, twin_ucs_page(ucs4)))
idx = 0;
return font->outlines + font->cur_page->offsets[idx];
}
static twin_fixed_t _twin_glyph_width (twin_text_info_t *info,
const signed char *b)
{
twin_fixed_t right = FX(twin_glyph_right(b), info) + info->pen.x * 2;
twin_fixed_t right_side_bearing;
twin_fixed_t width;
if (info->snap)
right = SNAPI(_twin_snap (right, info->snap_x, info->n_snap_x));
right_side_bearing = right + info->margin.x;
width = right_side_bearing + info->margin.x;
DBGMSG(("gw: g_right=%d right=%f, rsb=%f, margin_x=%f, width=%f\n",
twin_glyph_right(b), F(right), F(right_side_bearing), F(info->margin.x), F(width)));
return width;
}
void twin_text_metrics_ucs4 (twin_path_t *path,
twin_ucs4_t ucs4,
twin_text_metrics_t *m)
{
twin_font_t *font = g_twin_font;
const signed char *b = _twin_g_base (font, ucs4);
twin_text_info_t info;
twin_fixed_t left, right, ascent, descent;
twin_fixed_t font_spacing;
twin_fixed_t font_descent;
twin_fixed_t font_ascent;
twin_fixed_t margin_x, margin_y;
_twin_text_compute_info (path, font, &info);
if (info.snap)
_twin_text_compute_snap (&info, b);
left = FX(twin_glyph_left(b), &info);
right = FX(twin_glyph_right(b), &info) + info.pen.x * 2;
ascent = FY(twin_glyph_ascent(b), &info) + info.pen.y * 2;
descent = FY(twin_glyph_descent(b), &info);
margin_x = info.margin.x;
margin_y = info.margin.y;
font_spacing = FY(TWIN_GFIXED_ONE, &info);
font_descent = font_spacing / 3;
font_ascent = font_spacing - font_descent;
if (info.snap)
{
left = SNAPI(_twin_snap (left, info.snap_x, info.n_snap_x));
right = SNAPI(_twin_snap (right, info.snap_x, info.n_snap_x));
ascent = SNAPI(_twin_snap (ascent, info.snap_y, info.n_snap_y));
descent = SNAPI(_twin_snap (descent, info.snap_y, info.n_snap_y));
font_descent = SNAPI(font_descent);
font_ascent = SNAPI(font_ascent);
left = twin_fixed_mul (left, info.reverse_scale.x);
right = twin_fixed_mul (right, info.reverse_scale.x);
ascent = twin_fixed_mul (ascent, info.reverse_scale.y);
descent = twin_fixed_mul (descent, info.reverse_scale.y);
font_descent = twin_fixed_mul (font_descent, info.reverse_scale.y);
font_ascent = twin_fixed_mul (font_ascent, info.reverse_scale.y);
margin_x = twin_fixed_mul (margin_x, info.reverse_scale.x);
margin_y = twin_fixed_mul (margin_y, info.reverse_scale.y);
}
m->left_side_bearing = left + margin_x;
m->right_side_bearing = right + margin_x;
m->ascent = ascent;
m->descent = descent;
m->width = m->right_side_bearing + margin_x;
m->font_ascent = font_ascent + margin_y;
m->font_descent = font_descent + margin_y;
}
static const signed char *twin_glyph_draw(twin_font_t *font,
const signed char *b)
{
if (font->type == TWIN_FONT_TYPE_STROKE)
return twin_glyph_snap_y(b) + twin_glyph_n_snap_y(b);
return b + 4;
}
void twin_path_ucs4 (twin_path_t *path, twin_ucs4_t ucs4)
{
twin_font_t *font = g_twin_font;
const signed char *b = _twin_g_base (font, ucs4);
const signed char *g = twin_glyph_draw(font, b);
twin_spoint_t origin;
twin_fixed_t x1, y1, x2, y2, x3, y3, _x1, _y1;
twin_path_t *stroke;
twin_path_t *pen = NULL;
twin_fixed_t width;
twin_text_info_t info;
signed char op;
_twin_text_compute_info (path, font, &info);
if (info.snap)
_twin_text_compute_snap (&info, b);
origin = _twin_path_current_spoint (path);
stroke = twin_path_create ();
twin_path_set_matrix (stroke, info.matrix);
if (font->type == TWIN_FONT_TYPE_STROKE)
pen = _twin_text_compute_pen (&info);
x1 = y1 = 0;
for (;;) {
switch ((op = *g++)) {
case 'm':
x1 = FX(*g++, &info);
y1 = FY(*g++, &info);
if (info.snap)
{
x1 = _twin_snap (x1, info.snap_x, info.n_snap_x);
y1 = _twin_snap (y1, info.snap_y, info.n_snap_y);
}
DBGMSG (("m %9.4f %9.4f\n",
S(_twin_matrix_x (&stroke->state.matrix, x1, y1)),
S(_twin_matrix_y (&stroke->state.matrix, x1, y1))));
twin_path_move (stroke, x1, y1);
continue;
case 'l':
x1 = FX(*g++, &info);
y1 = FY(*g++, &info);
if (info.snap)
{
x1 = _twin_snap (x1, info.snap_x, info.n_snap_x);
y1 = _twin_snap (y1, info.snap_y, info.n_snap_y);
}
DBGMSG (("l %9.4f %9.4f\n",
S(_twin_matrix_x (&stroke->state.matrix, x1, y1)),
S(_twin_matrix_y (&stroke->state.matrix, x1, y1))));
twin_path_draw (stroke, x1, y1);
continue;
case 'c':
x3 = FX(*g++, &info);
y3 = FY(*g++, &info);
x2 = FX(*g++, &info);
y2 = FY(*g++, &info);
x1 = FX(*g++, &info);
y1 = FY(*g++, &info);
if (info.snap)
{
x3 = _twin_snap (x3, info.snap_x, info.n_snap_x);
y3 = _twin_snap (y3, info.snap_y, info.n_snap_y);
x2 = _twin_snap (x2, info.snap_x, info.n_snap_x);
y2 = _twin_snap (y2, info.snap_y, info.n_snap_y);
x1 = _twin_snap (x1, info.snap_x, info.n_snap_x);
y1 = _twin_snap (y1, info.snap_y, info.n_snap_y);
}
twin_path_curve (stroke, x3, y3, x2, y2, x1, y1);
continue;
case '2':
_x1 = FX(*g++, &info);
_y1 = FY(*g++, &info);
x3 = x1 + 2 * (_x1 - x1) / 3;
y3 = y1 + 2 * (_y1 - y1) / 3;
x1 = FX(*g++, &info);
y1 = FY(*g++, &info);
x2 = x1 + 2 * (_x1 - x1) / 3;
y2 = y1 + 2 * (_y1 - y1) / 3;
twin_path_curve (stroke, x3, y3, x2, y2, x1, y1);
continue;
case 'e':
break;
default:
DBGMSG (("unknown font op 0x%02x '%c'\n", op, op));
}
break;
}
if (font->type == TWIN_FONT_TYPE_STROKE) {
twin_path_convolve (path, stroke, pen);
twin_path_destroy (pen);
} else
twin_path_append(path, stroke);
twin_path_destroy (stroke);
width = _twin_glyph_width (&info, b);
_twin_path_smove (path,
origin.x + _twin_matrix_dx (&info.matrix, width, 0),
origin.y + _twin_matrix_dy (&info.matrix, width, 0));
}
twin_fixed_t twin_width_ucs4 (twin_path_t *path, twin_ucs4_t ucs4)
{
twin_text_metrics_t metrics;
twin_text_metrics_ucs4 (path, ucs4, &metrics);
return metrics.width;
}
static int _twin_utf8_to_ucs4 (const char *src_orig,
twin_ucs4_t *dst)
{
const char *src = src_orig;
char s;
int extra;
twin_ucs4_t result;
s = *src++;
if (!s)
return 0;
if (!(s & 0x80))
{
result = s;
extra = 0;
}
else if (!(s & 0x40))
{
return -1;
}
else if (!(s & 0x20))
{
result = s & 0x1f;
extra = 1;
}
else if (!(s & 0x10))
{
result = s & 0xf;
extra = 2;
}
else if (!(s & 0x08))
{
result = s & 0x07;
extra = 3;
}
else if (!(s & 0x04))
{
result = s & 0x03;
extra = 4;
}
else if ( ! (s & 0x02))
{
result = s & 0x01;
extra = 5;
}
else
{
return -1;
}
while (extra--)
{
result <<= 6;
s = *src++;
if (!s)
return -1;
if ((s & 0xc0) != 0x80)
return -1;
result |= s & 0x3f;
}
*dst = result;
return src - src_orig;
}
void twin_path_utf8 (twin_path_t *path, const char *string)
{
int len;
twin_ucs4_t ucs4;
while ((len = _twin_utf8_to_ucs4(string, &ucs4)) > 0)
{
twin_path_ucs4 (path, ucs4);
string += len;
}
}
twin_fixed_t twin_width_utf8 (twin_path_t *path, const char *string)
{
int len;
twin_ucs4_t ucs4;
twin_fixed_t w = 0;
while ((len = _twin_utf8_to_ucs4(string, &ucs4)) > 0)
{
w += twin_width_ucs4 (path, ucs4);
string += len;
}
return w;
}
void twin_text_metrics_utf8 (twin_path_t *path,
const char *string,
twin_text_metrics_t *m)
{
int len;
twin_ucs4_t ucs4;
twin_fixed_t w = 0;
twin_text_metrics_t c;
twin_bool_t first = TWIN_TRUE;
while ((len = _twin_utf8_to_ucs4(string, &ucs4)) > 0)
{
twin_text_metrics_ucs4 (path, ucs4, &c);
if (first)
{
*m = c;
first = TWIN_FALSE;
}
else
{
c.left_side_bearing += w;
c.right_side_bearing += w;
c.width += w;
if (c.left_side_bearing < m->left_side_bearing)
m->left_side_bearing = c.left_side_bearing;
if (c.right_side_bearing > m->right_side_bearing)
m->right_side_bearing = c.right_side_bearing;
if (c.width > m->width)
m->width = c.width;
if (c.ascent > m->ascent)
m->ascent = c.ascent;
if (c.descent > m->descent)
m->descent = c.descent;
}
w = c.width;
string += len;
}
}