kolibrios/contrib/media/updf/fitz/dev_text.c

419 lines
9.2 KiB
C
Raw Permalink Normal View History

#include "fitz.h"
#define LINE_DIST 0.9f
#define SPACE_DIST 0.2f
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_ADVANCES_H
typedef struct fz_text_device_s fz_text_device;
struct fz_text_device_s
{
fz_point point;
fz_text_span *head;
fz_text_span *span;
};
fz_text_span *
fz_new_text_span(void)
{
fz_text_span *span;
span = fz_malloc(sizeof(fz_text_span));
span->font = NULL;
span->wmode = 0;
span->size = 0;
span->len = 0;
span->cap = 0;
span->text = NULL;
span->next = NULL;
span->eol = 0;
return span;
}
void
fz_free_text_span(fz_text_span *span)
{
if (span->font)
fz_drop_font(span->font);
if (span->next)
fz_free_text_span(span->next);
fz_free(span->text);
fz_free(span);
}
static void
fz_add_text_char_imp(fz_text_span *span, int c, fz_bbox bbox)
{
if (span->len + 1 >= span->cap)
{
span->cap = span->cap > 1 ? (span->cap * 3) / 2 : 80;
span->text = fz_realloc(span->text, span->cap, sizeof(fz_text_char));
}
span->text[span->len].c = c;
span->text[span->len].bbox = bbox;
span->len ++;
}
static fz_bbox
fz_split_bbox(fz_bbox bbox, int i, int n)
{
float w = (float)(bbox.x1 - bbox.x0) / n;
float x0 = bbox.x0;
bbox.x0 = x0 + i * w;
bbox.x1 = x0 + (i + 1) * w;
return bbox;
}
static void
fz_add_text_char(fz_text_span **last, fz_font *font, float size, int wmode, int c, fz_bbox bbox)
{
fz_text_span *span = *last;
if (!span->font)
{
span->font = fz_keep_font(font);
span->size = size;
}
if ((span->font != font || span->size != size || span->wmode != wmode) && c != 32)
{
span = fz_new_text_span();
span->font = fz_keep_font(font);
span->size = size;
span->wmode = wmode;
(*last)->next = span;
*last = span;
}
switch (c)
{
case -1: /* ignore when one unicode character maps to multiple glyphs */
break;
case 0xFB00: /* ff */
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 0, 2));
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 1, 2));
break;
case 0xFB01: /* fi */
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 0, 2));
fz_add_text_char_imp(span, 'i', fz_split_bbox(bbox, 1, 2));
break;
case 0xFB02: /* fl */
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 0, 2));
fz_add_text_char_imp(span, 'l', fz_split_bbox(bbox, 1, 2));
break;
case 0xFB03: /* ffi */
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 0, 3));
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 1, 3));
fz_add_text_char_imp(span, 'i', fz_split_bbox(bbox, 2, 3));
break;
case 0xFB04: /* ffl */
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 0, 3));
fz_add_text_char_imp(span, 'f', fz_split_bbox(bbox, 1, 3));
fz_add_text_char_imp(span, 'l', fz_split_bbox(bbox, 2, 3));
break;
case 0xFB05: /* long st */
case 0xFB06: /* st */
fz_add_text_char_imp(span, 's', fz_split_bbox(bbox, 0, 2));
fz_add_text_char_imp(span, 't', fz_split_bbox(bbox, 1, 2));
break;
default:
fz_add_text_char_imp(span, c, bbox);
break;
}
}
static void
fz_divide_text_chars(fz_text_span **last, int n, fz_bbox bbox)
{
fz_text_span *span = *last;
int i, x;
x = span->len - n;
if (x >= 0)
for (i = 0; i < n; i++)
span->text[x + i].bbox = fz_split_bbox(bbox, i, n);
}
static void
fz_add_text_newline(fz_text_span **last, fz_font *font, float size, int wmode)
{
fz_text_span *span;
span = fz_new_text_span();
span->font = fz_keep_font(font);
span->size = size;
span->wmode = wmode;
(*last)->eol = 1;
(*last)->next = span;
*last = span;
}
void
fz_debug_text_span_xml(fz_text_span *span)
{
char buf[10];
int c, n, k, i;
printf("<span font=\"%s\" size=\"%g\" wmode=\"%d\" eol=\"%d\">\n",
span->font ? span->font->name : "NULL", span->size, span->wmode, span->eol);
for (i = 0; i < span->len; i++)
{
printf("\t<char ucs=\"");
c = span->text[i].c;
if (c < 128)
putchar(c);
else
{
n = runetochar(buf, &c);
for (k = 0; k < n; k++)
putchar(buf[k]);
}
printf("\" bbox=\"%d %d %d %d\" />\n",
span->text[i].bbox.x0,
span->text[i].bbox.y0,
span->text[i].bbox.x1,
span->text[i].bbox.y1);
}
printf("</span>\n");
if (span->next)
fz_debug_text_span_xml(span->next);
}
void
fz_debug_text_span(fz_text_span *span)
{
char buf[10];
int c, n, k, i;
for (i = 0; i < span->len; i++)
{
c = span->text[i].c;
if (c < 128)
putchar(c);
else
{
n = runetochar(buf, &c);
for (k = 0; k < n; k++)
putchar(buf[k]);
}
}
if (span->eol)
putchar('\n');
if (span->next)
fz_debug_text_span(span->next);
}
static void
fz_text_extract_span(fz_text_span **last, fz_text *text, fz_matrix ctm, fz_point *pen)
{
fz_font *font = text->font;
FT_Face face = font->ft_face;
fz_matrix tm = text->trm;
fz_matrix trm;
float size;
float adv;
fz_rect rect;
fz_point dir, ndir;
fz_point delta, ndelta;
float dist, dot;
float ascender = 1;
float descender = 0;
int multi;
int i, err;
if (text->len == 0)
return;
if (font->ft_face)
{
err = FT_Set_Char_Size(font->ft_face, 64, 64, 72, 72);
if (err)
fz_warn("freetype set character size: %s", ft_error_string(err));
ascender = (float)face->ascender / face->units_per_EM;
descender = (float)face->descender / face->units_per_EM;
}
rect = fz_empty_rect;
if (text->wmode == 0)
{
dir.x = 1;
dir.y = 0;
}
else
{
dir.x = 0;
dir.y = 1;
}
tm.e = 0;
tm.f = 0;
trm = fz_concat(tm, ctm);
dir = fz_transform_vector(trm, dir);
dist = sqrtf(dir.x * dir.x + dir.y * dir.y);
ndir.x = dir.x / dist;
ndir.y = dir.y / dist;
size = fz_matrix_expansion(trm);
multi = 1;
for (i = 0; i < text->len; i++)
{
if (text->items[i].gid < 0)
{
fz_add_text_char(last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
multi ++;
fz_divide_text_chars(last, multi, fz_round_rect(rect));
continue;
}
multi = 1;
/* Calculate new pen location and delta */
tm.e = text->items[i].x;
tm.f = text->items[i].y;
trm = fz_concat(tm, ctm);
delta.x = pen->x - trm.e;
delta.y = pen->y - trm.f;
if (pen->x == -1 && pen->y == -1)
delta.x = delta.y = 0;
dist = sqrtf(delta.x * delta.x + delta.y * delta.y);
/* Add space and newlines based on pen movement */
if (dist > 0)
{
ndelta.x = delta.x / dist;
ndelta.y = delta.y / dist;
dot = ndelta.x * ndir.x + ndelta.y * ndir.y;
if (dist > size * LINE_DIST)
{
fz_add_text_newline(last, font, size, text->wmode);
}
else if (fabsf(dot) > 0.95f && dist > size * SPACE_DIST)
{
if ((*last)->len > 0 && (*last)->text[(*last)->len - 1].c != ' ')
{
fz_rect spacerect;
spacerect.x0 = -0.2f;
spacerect.y0 = 0;
spacerect.x1 = 0;
spacerect.y1 = 1;
spacerect = fz_transform_rect(trm, spacerect);
fz_add_text_char(last, font, size, text->wmode, ' ', fz_round_rect(spacerect));
}
}
}
/* Calculate bounding box and new pen position based on font metrics */
if (font->ft_face)
{
FT_Fixed ftadv = 0;
int mask = FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_IGNORE_TRANSFORM;
/* TODO: freetype returns broken vertical metrics */
/* if (text->wmode) mask |= FT_LOAD_VERTICAL_LAYOUT; */
FT_Get_Advance(font->ft_face, text->items[i].gid, mask, &ftadv);
adv = ftadv / 65536.0f;
rect.x0 = 0;
rect.y0 = descender;
rect.x1 = adv;
rect.y1 = ascender;
}
else
{
adv = font->t3widths[text->items[i].gid];
rect.x0 = 0;
rect.y0 = descender;
rect.x1 = adv;
rect.y1 = ascender;
}
rect = fz_transform_rect(trm, rect);
pen->x = trm.e + dir.x * adv;
pen->y = trm.f + dir.y * adv;
fz_add_text_char(last, font, size, text->wmode, text->items[i].ucs, fz_round_rect(rect));
}
}
static void
fz_text_fill_text(void *user, fz_text *text, fz_matrix ctm,
fz_colorspace *colorspace, float *color, float alpha)
{
fz_text_device *tdev = user;
fz_text_extract_span(&tdev->span, text, ctm, &tdev->point);
}
static void
fz_text_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm,
fz_colorspace *colorspace, float *color, float alpha)
{
fz_text_device *tdev = user;
fz_text_extract_span(&tdev->span, text, ctm, &tdev->point);
}
static void
fz_text_clip_text(void *user, fz_text *text, fz_matrix ctm, int accumulate)
{
fz_text_device *tdev = user;
fz_text_extract_span(&tdev->span, text, ctm, &tdev->point);
}
static void
fz_text_clip_stroke_text(void *user, fz_text *text, fz_stroke_state *stroke, fz_matrix ctm)
{
fz_text_device *tdev = user;
fz_text_extract_span(&tdev->span, text, ctm, &tdev->point);
}
static void
fz_text_ignore_text(void *user, fz_text *text, fz_matrix ctm)
{
fz_text_device *tdev = user;
fz_text_extract_span(&tdev->span, text, ctm, &tdev->point);
}
static void
fz_text_free_user(void *user)
{
fz_text_device *tdev = user;
tdev->span->eol = 1;
/* TODO: unicode NFC normalization */
/* TODO: bidi logical reordering */
fz_free(tdev);
}
fz_device *
fz_new_text_device(fz_text_span *root)
{
fz_device *dev;
fz_text_device *tdev = fz_malloc(sizeof(fz_text_device));
tdev->head = root;
tdev->span = root;
tdev->point.x = -1;
tdev->point.y = -1;
dev = fz_new_device(tdev);
dev->hints = FZ_IGNORE_IMAGE | FZ_IGNORE_SHADE;
dev->free_user = fz_text_free_user;
dev->fill_text = fz_text_fill_text;
dev->stroke_text = fz_text_stroke_text;
dev->clip_text = fz_text_clip_text;
dev->clip_stroke_text = fz_text_clip_stroke_text;
dev->ignore_text = fz_text_ignore_text;
return dev;
}