#include "fitz.h" #define LINE_DIST 0.9f #define SPACE_DIST 0.2f #include #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("\n", span->font ? span->font->name : "NULL", span->size, span->wmode, span->eol); for (i = 0; i < span->len; i++) { printf("\ttext[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("\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; }