| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| typedef struct _HeliFontMap HeliFontMap; |
| typedef struct _HeliFontInstance HeliFontInstance; |
| typedef struct _HeliCharDesc HeliCharDesc; |
| |
| typedef struct _HeliGlyph { |
| HeliFontMap *map; |
| HeliFontInstance *fi; |
| int code; |
| int index; |
| double l, t, r, b; |
| double advance; |
| } HeliGlyph; |
| |
| struct _HeliFontMap { |
| HeliFontMap *prev, *next; |
| unsigned int texid; |
| int count; |
| HeliGlyph glyphs[FONT_MAP_FULLCNT]; |
| }; |
| |
| struct _HeliFontInstance { |
| char *path; |
| FT_Face face; |
| double height; |
| double xheight; |
| double ascender; |
| double descender; |
| HeliGlyph *glyphs[FONT_MAX_GLYPHS]; |
| int refcount; |
| }; |
| |
| struct _HeliCharDesc { |
| HeliGlyph *glyph; |
| int charpos; |
| double pos; |
| double x, y; |
| HeliCharDesc *next; |
| }; |
| |
| typedef struct _HeliLineDesc { |
| int begin, end; |
| double x, y; |
| double l, t, r, b; |
| double xheight; |
| double ascender; |
| double descender; |
| double height; |
| double width; |
| } HeliLineDesc; |
| |
| struct _TextLayout { |
| Font font; |
| int linesCount; |
| HeliLineDesc *lines; |
| HeliCharDesc *chars; |
| double l, t, r, b; |
| double ll, lt, lr, lb; |
| }; |
| |
| struct _Font { |
| HeliFontInstance *instance; |
| }; |
| |
| |
| |
| static int ftInitialized = 0; |
| static FT_Library ftLibrary = NULL; |
| static HeliArray fontCache; |
| static HeliFontMap *fontMapFirst, *fontMapLast; |
| static HeliGlyph blankGlyph; |
| static const char blankPath[] = ""; |
| static Font defaultFont = NULL; |
| static Font unicodeFont = NULL; |
| static int memFontIndex = 0; |
| |
| static HeliFontMap* createFontMap() { |
| HeliFontMap *map = calloc(1, sizeof(*map)); |
| map->prev = fontMapLast; |
| *(fontMapLast ? &fontMapLast->next : &fontMapFirst) = map; |
| fontMapLast = map; |
| |
| for(int i = 0; i < FONT_MAP_FULLCNT; ++i) |
| map->glyphs[i].map = map; |
| |
| unsigned int prevtex = 0; |
| glGetIntegerv(GL_TEXTURE_BINDING_2D, (int*)&prevtex); |
| glGenTextures(1, &map->texid); |
| glBindTexture(GL_TEXTURE_2D, map->texid); |
| |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); |
| glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.7f); |
| |
| unsigned char *buffer = (unsigned char*)calloc(1, FONT_MAP_TEXSIZE*FONT_MAP_TEXSIZE); |
| for(int level = 0, size = FONT_MAP_TEXSIZE; size > 0; ++level, size >>= 1) |
| glTexImage2D(GL_TEXTURE_2D, level, GL_ALPHA, size, size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, buffer); |
| free(buffer); |
| |
| int maxLevel = 0; |
| for(int border = FONT_MAP_BORDERSIZE; border > 0; border >>= 1) ++maxLevel; |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel - 1); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, maxLevel - 1); |
| |
| glBindTexture(GL_TEXTURE_2D, prevtex); |
| return map; |
| } |
| |
| static void destroyFontMap(HeliFontMap *map) { |
| assert(!map->count); |
| glDeleteTextures(1, &map->texid); |
| *(map->prev ? &map->prev->next : &fontMapFirst) = map->next; |
| *(map->next ? &map->next->prev : &fontMapLast ) = map->prev; |
| free(map); |
| } |
| |
| |
| static HeliGlyph* loadGlyph(HeliFontInstance *fi, int code) { |
| int index = FT_Get_Char_Index(fi->face, (unsigned int)code); |
| if (!index) return &blankGlyph; |
| |
| if (FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)) |
| return &blankGlyph; |
| |
| HeliFontMap *map; |
| for(map = fontMapFirst; map; map = map->next) |
| if (map->count < FONT_MAP_FULLCNT) break; |
| if (!map) map = createFontMap(); |
| |
| HeliGlyph *g = NULL; |
| for(int i = 0; i < FONT_MAP_FULLCNT; ++i) |
| if (!map->glyphs[i].fi) { g = &map->glyphs[i]; break; } |
| assert(g); |
| |
| const double ftkf = 1/64.0; |
| const FT_Glyph_Metrics *metrics = &fi->face->glyph->metrics; |
| g->l = metrics->horiBearingX*ftkf; |
| g->r = (metrics->horiBearingX + metrics->width)*ftkf; |
| g->t = -metrics->horiBearingY*ftkf; |
| g->b = (-metrics->horiBearingY + metrics->height)*ftkf; |
| g->advance = metrics->horiAdvance*ftkf; |
| |
| unsigned char buffer[FONT_MAP_GLYPHSIZE*FONT_MAP_GLYPHSIZE] = {}; |
| if (metrics->width > 0 && metrics->height > 0) { |
| FT_Pos size = metrics->width > metrics->height ? metrics->width : metrics->height; |
| FT_Matrix matrix = {}; |
| matrix.xx = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/size; |
| matrix.yy = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/size; |
| FT_Vector offset = {}; |
| offset.x = 64 - metrics->horiBearingX; |
| offset.y = -(64 + metrics->horiBearingY - metrics->height); |
| FT_Set_Transform(fi->face, &matrix, &offset); |
| int success = !FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING) |
| && !FT_Render_Glyph(fi->face->glyph, FT_RENDER_MODE_NORMAL); |
| FT_Set_Transform(fi->face, NULL, NULL); |
| if (!success) return &blankGlyph; |
| |
| const FT_Bitmap *bitmap = &fi->face->glyph->bitmap; |
| int dr = 0; |
| int dc = 0; |
| int r0 = -dr; |
| int r1 = r0 + bitmap->rows; |
| int c0 = -dc; |
| int c1 = c0 + bitmap->width; |
| if (r0 < 0) r0 = 0; |
| if (c0 < 0) c0 = 0; |
| if (r1 > FONT_MAP_GLYPHWORKSIZE) r1 = FONT_MAP_GLYPHWORKSIZE; |
| if (c1 > FONT_MAP_GLYPHWORKSIZE) c1 = FONT_MAP_GLYPHWORKSIZE; |
| for(int r = r0; r < r1; ++r) |
| for(int c = c0; c < c1; ++c) |
| buffer[r*FONT_MAP_GLYPHSIZE + c] = *(bitmap->buffer + bitmap->pitch*(r + dr) + c + dc); |
| } |
| |
| unsigned int prevtex = 0; |
| glGetIntegerv(GL_TEXTURE_BINDING_2D, (int*)&prevtex); |
| glBindTexture(GL_TEXTURE_2D, g->map->texid); |
| int gi = g - g->map->glyphs; |
| int r = gi/FONT_MAP_CNT; |
| int c = gi%FONT_MAP_CNT; |
| int size = FONT_MAP_GLYPHSIZE; |
| for(int level = 0, border = FONT_MAP_BORDERSIZE; border > 0; border >>= 1, ++level) { |
| glTexSubImage2D( |
| GL_TEXTURE_2D, |
| level, |
| c*size, |
| r*size, |
| size, |
| size, |
| GL_ALPHA, |
| GL_UNSIGNED_BYTE, |
| buffer ); |
| if (border > 1) { |
| size >>= 1; |
| for(int r = 0; r < size; ++r) |
| for(int c = 0; c < size; ++c) |
| buffer[r*size + c] = (unsigned char)(( |
| (unsigned int)buffer[((r*2 + 0)*size + c)*2 + 0] |
| + (unsigned int)buffer[((r*2 + 0)*size + c)*2 + 1] |
| + (unsigned int)buffer[((r*2 + 1)*size + c)*2 + 0] |
| + (unsigned int)buffer[((r*2 + 1)*size + c)*2 + 1] + 2) >> 2 ); |
| } |
| } |
| glBindTexture(GL_TEXTURE_2D, prevtex); |
| |
| g->fi = fi; |
| g->code = code; |
| g->index = index; |
| ++g->map->count; |
| return g; |
| } |
| |
| |
| static void unloadGlyph(HeliGlyph *g) { |
| g->fi = NULL; |
| g->code = 0; |
| g->index = 0; |
| if (g->map && --g->map->count <= 0) |
| destroyFontMap(g->map); |
| } |
| |
| |
| static HeliFontInstance* loadFont(const char *path) { |
| HeliFontInstance *fi = calloc(1, sizeof(*fi)); |
| fi->path = heliStringCopy(path); |
| |
| if (!ftInitialized) { |
| if (FT_Init_FreeType(&ftLibrary)) |
| fprintf(stderr, "helianthus: cannot initialize FreeType library\n"); |
| ftInitialized = TRUE; |
| } |
| if (ftLibrary) { |
| if (strncmp(path, "helimem:", 8) == 0) { |
| void *data = NULL; |
| int size = 0; |
| fi->face = NULL; |
| if (sscanf(path, "helimem:%p:%d:", &data, &size) != EOF && data && size > 0) { |
| FT_Open_Args args = {}; |
| args.flags = FT_OPEN_MEMORY; |
| args.memory_base = (FT_Byte*)data; |
| args.memory_size = (FT_Long)size; |
| if (FT_Open_Face(ftLibrary, &args, 0, &fi->face)) |
| fi->face = NULL; |
| } |
| if (!fi->face) |
| fprintf(stderr, "helianthus: cannot load font from memory\n"); |
| } else { |
| if (FT_New_Face(ftLibrary, path, 0, &fi->face)) { |
| fprintf(stderr, "helianthus: cannot load font from file: %s\n", path); |
| fi->face = NULL; |
| } |
| } |
| |
| if (fi->face) { |
| FT_Set_Char_Size(fi->face, 0, FONT_BASE_SIZE*64, 72, 72); |
| fi->height = fi->face->size->metrics.height/64.0; |
| double ascender = fi->face->size->metrics.ascender/64.0; |
| |
| unsigned int xchars[] = { 'x', 'z', 'v' }; |
| int count = 0; |
| double xheight = 0; |
| for(int i = 0; i < (int)(sizeof(xchars)/sizeof(*xchars)); ++i) { |
| int index = FT_Get_Char_Index(fi->face, xchars[i]); |
| if (!index) continue; |
| if (FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)) |
| continue; |
| xheight += fi->face->glyph->metrics.horiBearingY/64.0; |
| ++count; |
| } |
| if (count > 0) xheight /= count; |
| if (xheight <= HELI_PRECISION || xheight > ascender) |
| xheight = ascender; |
| |
| fi->ascender = ascender; |
| fi->xheight = xheight; |
| fi->descender = fi->face->size->metrics.descender/64.0; |
| } |
| } |
| |
| return fi; |
| } |
| |
| |
| static void unloadFont(HeliFontInstance *fi) { |
| assert(!fi->refcount); |
| for(int i = 0; i < FONT_MAX_GLYPHS; ++i) |
| if (fi->glyphs[i]) unloadGlyph(fi->glyphs[i]); |
| free(fi->path); |
| if (fi->face) FT_Done_Face(fi->face); |
| free(fi); |
| } |
| |
| |
| static HeliGlyph* getGlyph(HeliFontInstance *fi, int code) { |
| if (code < 0 || code >= FONT_MAX_GLYPHS) return &blankGlyph; |
| if (!fi->glyphs[code]) fi->glyphs[code] = loadGlyph(fi, code); |
| return fi->glyphs[code]; |
| } |
| |
| |
| Font createFont(const char *path) { |
| if (!heliInitialized) return NULL; |
| if (!path) path = blankPath; |
| Font f = calloc(1, sizeof(*f)); |
| HeliPair *item = heliStringmapGet(&fontCache, path); |
| if (!item) item = heliStringmapAdd(&fontCache, path, loadFont(path), (HeliFreeCallback)&unloadFont); |
| f->instance = (HeliFontInstance*)item->value; |
| ++f->instance->refcount; |
| heliObjectRegister(f, (HeliFreeCallback)&fontDestroy); |
| return f; |
| } |
| |
| |
| Font createFontFromMemory(const void *data, int size) { |
| char buffer[1024] = {}; |
| sprintf(buffer, "helimem:%p:%d:%d", data, size, ++memFontIndex); |
| return createFont(buffer); |
| } |
| |
| |
| Font fontClone(Font font) { |
| if (!font) return NULL; |
| return createFont(font->instance ? font->instance->path : NULL); |
| } |
| |
| |
| void fontDestroy(Font f) { |
| heliObjectUnregister(f); |
| HeliDrawingState *ss = heliDrawingGetState(); |
| for(HeliDrawingState *s = heliDrawingGetStateStack(); s <= ss; ++s) |
| { if (ss->font == f) ss->font = NULL; } |
| --f->instance->refcount; |
| if (f->instance->refcount <= 0) |
| heliStringmapRemove(&fontCache, f->instance->path); |
| free(f); |
| } |
| |
| void textAlign(HAlign hor, VAlign vert) { |
| HeliDrawingState *s = heliDrawingGetState(); |
| s->horAlign = hor; |
| s->vertAlign = vert; |
| } |
| void textFontDefault() |
| { heliDrawingGetState()->font = NULL; } |
| void textFont(Font f) |
| { heliDrawingGetState()->font = f; } |
| void textSize(double size) |
| { heliDrawingGetState()->fontSize = size; } |
| |
| |
| static void expand(double *min, double *max, double x) { |
| if (*min > x) *min = x; |
| if (*max < x) *max = x; |
| } |
| |
| |
| TextLayout createTextLayout(const char *text) { |
| TextLayout layout = calloc(1, sizeof(*layout)); |
| |
| if (!text) return layout; |
| |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| |
| if (!defaultFont && heliBlobDefaultFontSize) |
| defaultFont = createFontFromMemory(heliBlobDefaultFont, heliBlobDefaultFontSize); |
| if (!unicodeFont && heliBlobUnicodeFontSize) |
| unicodeFont = createFontFromMemory(heliBlobUnicodeFont, heliBlobUnicodeFontSize); |
| |
| HeliFontInstance *fonts[10]; |
| int fontsCount = 0; |
| if ( drawingState->font |
| && drawingState->font->instance |
| && drawingState->font->instance->face ) |
| fonts[fontsCount++] = drawingState->font->instance; |
| if ( defaultFont |
| && defaultFont->instance |
| && defaultFont->instance->face ) |
| fonts[fontsCount++] = defaultFont->instance; |
| if ( unicodeFont |
| && unicodeFont->instance |
| && unicodeFont->instance->face ) |
| fonts[fontsCount++] = unicodeFont->instance; |
| |
| if (!fontsCount) |
| return layout; |
| |
| int linesAllocated = 256; |
| layout->linesCount = 1; |
| layout->lines = calloc(linesAllocated, sizeof(HeliLineDesc)); |
| HeliLineDesc *line = layout->lines; |
| line->height = fonts[0]->height; |
| line->xheight = fonts[0]->xheight; |
| line->ascender = fonts[0]->ascender; |
| line->descender = fonts[0]->descender; |
| layout->chars = calloc(strlen(text) + 1, sizeof(HeliCharDesc)); |
| if ( drawingState->font |
| && drawingState->font != defaultFont |
| && drawingState->font != unicodeFont |
| && drawingState->font->instance == fonts[0] ) |
| layout->font = fontClone(drawingState->font); |
| |
| |
| const char *c = text; |
| while(1) { |
| const char *charpos = c; |
| int code = FONT_BADCHAR_CODE; |
| while((*c & 0xC0) == 0x80) ++c; |
| if ((*c & 0x80) == 0x00) { |
| code = *c; |
| } else |
| if ((*c & 0xE0) == 0xC0) { |
| if ((c[1] & 0xC0) == 0x80) { |
| code = ((c[0] & 0x1F) << 6) | (c[1] & 0x3F); |
| c += 1; |
| } |
| } else |
| if ((*c & 0xF0) == 0xE0) { |
| if ((c[1] & 0xC0) == 0x80 && (c[2] & 0xC0) == 0x80) { |
| code = ((c[0] & 0x0F) << 12) | ((c[1] & 0x3F) << 6) | (c[2] & 0x3F); |
| c += 2; |
| } |
| } |
| ++c; |
| |
| HeliGlyph *glyph = &blankGlyph; |
| int code2 = (code == 0 || code == 10 || code == 13) ? 32 : code; |
| for(int i = 0; i < fontsCount && !glyph->fi; ++i) |
| glyph = getGlyph(fonts[i], code2); |
| for(int i = 0; i < fontsCount && !glyph->fi; ++i) |
| glyph = getGlyph(fonts[i], FONT_BADCHAR_CODE); |
| if (!glyph->fi) continue; |
| |
| HeliCharDesc *cc = &layout->chars[line->end++]; |
| cc->charpos = charpos - text; |
| cc->glyph = glyph; |
| if (line->height < glyph->fi->height ) line->height = glyph->fi->height; |
| if (line->xheight < glyph->fi->xheight ) line->xheight = glyph->fi->xheight; |
| if (line->ascender < glyph->fi->ascender ) line->ascender = glyph->fi->ascender; |
| if (line->descender < glyph->fi->descender) line->descender = glyph->fi->descender; |
| if (line->begin < line->end - 1) { |
| HeliCharDesc *cp = cc - 1; |
| cc->pos = cp->pos + cp->glyph->advance; |
| FT_Vector kerning = {}; |
| if ( cp->glyph->fi == cc->glyph->fi |
| && !FT_Get_Kerning( |
| cc->glyph->fi->face, |
| cp->glyph->index, |
| cc->glyph->index, |
| FT_KERNING_UNFITTED, |
| &kerning )) |
| cc->pos += kerning.x/64.0; |
| if (line->width < cc->pos + cc->glyph->advance) |
| line->width = cc->pos + cc->glyph->advance; |
| } |
| |
| if ( line->begin >= line->end - 1 |
| || ( cc->glyph->l + HELI_PRECISION < cc->glyph->r |
| && code != 10 |
| && code != 13 |
| && code != 0 )) |
| { |
| expand(&line->l, &line->r, cc->pos + cc->glyph->l); |
| expand(&line->l, &line->r, cc->pos + cc->glyph->r); |
| } |
| expand(&line->t, &line->b, cc->glyph->t); |
| expand(&line->t, &line->b, cc->glyph->b); |
| expand(&line->t, &line->b, -glyph->fi->height); |
| |
| if (code == 10 || code == 13) { |
| if ((code == 10 && *c == 13) || (code == 13 && *c == 10)) ++c; |
| if (layout->linesCount >= linesAllocated) { |
| linesAllocated += linesAllocated/4; |
| layout->lines = realloc(layout->lines, linesAllocated*sizeof(HeliLineDesc)); |
| memset(layout->lines + layout->linesCount, 0, (linesAllocated - layout->linesCount)*sizeof(HeliLineDesc)); |
| line = &layout->lines[layout->linesCount - 1]; |
| } |
| ++line; |
| ++layout->linesCount; |
| line->begin = line->end = (line - 1)->end; |
| line->height = fonts[0]->height; |
| line->xheight = fonts[0]->xheight; |
| line->ascender = fonts[0]->ascender; |
| line->descender = fonts[0]->descender; |
| } |
| if (code == 0) break; |
| } |
| |
| int charsCount = layout->lines[layout->linesCount-1].end; |
| if (charsCount <= 0) return layout; |
| |
| |
| double lineHeight = layout->lines->height; |
| if (drawingState->vertAlign == VALIGN_CENTER) |
| lineHeight = layout->lines->xheight; |
| double l = 0; |
| double t = -layout->lines->height; |
| double r = 0; |
| double b = 0; |
| double ll = 0; |
| double lt = -lineHeight; |
| double lr = 0; |
| double lb = 0; |
| for(int i = 0; i < layout->linesCount; ++i) { |
| HeliLineDesc *line = layout->lines + i; |
| if (i > 0) line->y = (line-1)->y + line->height; |
| |
| if (l > line->l) l = line->l; |
| if (r < line->r) r = line->r; |
| if (t > line->t + line->y) t = line->t + line->y; |
| if (b < line->b + line->y) b = line->b + line->y; |
| |
| if (lr < line->r) lr = line->r; |
| if (lt > line->y - lineHeight) lt = line->y - lineHeight; |
| if (lb < line->y) lb = line->y; |
| } |
| |
| |
| double dx = drawingState->horAlign == HALIGN_RIGHT ? -lr |
| : drawingState->horAlign == HALIGN_CENTER ? -0.5*(ll + lr) |
| : -ll; |
| double dy = drawingState->vertAlign == VALIGN_BOTTOM ? -lb |
| : drawingState->vertAlign == VALIGN_CENTER ? -0.5*(lt + lb) |
| : -lt; |
| for(int i = 0; i < layout->linesCount; ++i) { |
| HeliLineDesc *line = layout->lines + i; |
| line->x = drawingState->horAlign == HALIGN_RIGHT ? -line->r |
| : drawingState->horAlign == HALIGN_CENTER ? -0.5*line->r |
| : 0; |
| line->y += dy; |
| for(int j = line->begin; j < line->end; ++j) { |
| HeliCharDesc *cc = layout->chars + j; |
| cc->x = cc->pos + line->x; |
| cc->y = line->y; |
| } |
| } |
| |
| |
| layout->l = l + dx; |
| layout->t = t + dy; |
| layout->r = r + dx; |
| layout->b = b + dy; |
| |
| layout->ll = ll + dx; |
| layout->lt = lt + dy; |
| layout->lr = lr + dx; |
| layout->lb = lb + dy; |
| |
| |
| for(int i = 0; i < charsCount-1; ++i) |
| layout->chars[i].next = &layout->chars[i+1]; |
| HeliCharDesc *first = NULL, *last = NULL, *current = layout->chars; |
| while(current) { |
| if (!current->next) { |
| current = current->next = first; |
| } else |
| if (current->glyph->map != current->next->glyph->map) { |
| *(last ? &last->next : &first) = current->next; |
| last = current->next; |
| current->next = current->next->next; |
| last->next = NULL; |
| } else { |
| current = current->next; |
| } |
| } |
| |
| return layout; |
| } |
| |
| |
| TextLayout createTextLayoutf(const char *format, ...) { |
| va_list args; |
| va_start(args, format); |
| |
| va_list args_copy; |
| va_copy(args_copy, args); |
| int size = vsnprintf(NULL, 0, format, args_copy); |
| va_end(args_copy); |
| if (size < 0) size = 0; |
| ++size; |
| char *buffer = calloc(1, size); |
| vsnprintf(buffer, size, format, args); |
| va_end(args); |
| |
| TextLayout layout = createTextLayout(buffer); |
| free(buffer); |
| return layout; |
| } |
| |
| |
| void textLayoutDestroy(TextLayout layout) { |
| if (layout) { |
| free(layout->lines); |
| free(layout->chars); |
| if (layout->font) fontDestroy(layout->font); |
| } |
| free(layout); |
| } |
| |
| |
| void textLayoutDrawSubstr(TextLayout layout, double x, double y, int start, int length) { |
| resetPath(); |
| |
| if (layout->linesCount <= 0) return; |
| assert(layout->lines); |
| assert(layout->chars); |
| int charsCount = layout->lines[layout->linesCount - 1].end; |
| if (charsCount <= 0) return; |
| |
| int textEnd = layout->chars[charsCount - 1].charpos; |
| int begin = start; |
| if (begin < 0) begin = 0; |
| long long endll = (long long)begin + length; |
| if (endll > textEnd) endll = textEnd; |
| int end = (int)endll; |
| if (begin >= end) return; |
| |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| if (fontScale <= HELI_PRECISION) return; |
| if ( drawingState->strokeColor[3] <= HELI_PRECISION |
| && drawingState->fillColor[3] <= HELI_PRECISION ) return; |
| |
| const double borderK = 0.5*FONT_MAP_BORDERSIZE/FONT_MAP_GLYPHWORKSIZE; |
| const double texStep = 1.0/FONT_MAP_CNT; |
| const double texBorder = 0.5*FONT_MAP_BORDERSIZE/FONT_MAP_TEXSIZE; |
| const double texGlyphSize = (double)FONT_MAP_GLYPHWORKSIZE/FONT_MAP_TEXSIZE; |
| |
| if (drawingState->antialiasing && !heliGLIsIntegerClipping()) |
| glEnable(GL_MULTISAMPLE); |
| glPushMatrix(); |
| glTranslated(x, y, 0); |
| glScaled(fontScale, fontScale, 1); |
| |
| if (drawingState->fillColor[3] > HELI_PRECISION) { |
| double strokeAlpha = drawingState->strokeColor[3]; |
| drawingState->strokeColor[3] = 0; |
| for(int i = 0; i < layout->linesCount; ++i) { |
| HeliLineDesc *line = layout->lines + i; |
| int found = FALSE; |
| double l = 0, r = 0; |
| for(int j = line->begin; j < line->end; ++j) { |
| HeliCharDesc *cc = layout->chars + j; |
| if (cc->charpos < begin || cc->charpos > end) continue; |
| if (!found) { l = r = cc->x; found = TRUE; } |
| expand(&l, &r, cc->x); |
| if (cc->glyph && cc->charpos < end) { |
| expand(&l, &r, cc->x + cc->glyph->l); |
| expand(&l, &r, cc->x + cc->glyph->r); |
| } |
| } |
| if (found) |
| rect(l, line->t + line->y, r-l, line->b - line->t); |
| } |
| drawingState->strokeColor[3] = strokeAlpha; |
| } |
| |
| if (drawingState->strokeColor[3] > HELI_PRECISION) { |
| glColor4dv(drawingState->strokeColor); |
| HeliFontMap *currentMap = NULL; |
| glEnable(GL_TEXTURE_2D); |
| glBegin(GL_QUADS); |
| for(HeliCharDesc *cc = layout->chars; cc; cc = cc->next) { |
| if (cc->charpos < begin || cc->charpos >= end) continue; |
| if ( !cc->glyph->map |
| || cc->glyph->r - cc->glyph->l <= HELI_PRECISION |
| || cc->glyph->b - cc->glyph->t <= HELI_PRECISION ) |
| continue; |
| if (currentMap != cc->glyph->map) { |
| glEnd(); |
| currentMap = cc->glyph->map; |
| glBindTexture(GL_TEXTURE_2D, currentMap->texid); |
| glBegin(GL_QUADS); |
| } |
| |
| double gw = cc->glyph->r - cc->glyph->l; |
| double gh = cc->glyph->b - cc->glyph->t; |
| double gs = gw > gh ? gw : gh; |
| double dx = gs*borderK; |
| double dy = gs*borderK; |
| double l = cc->x + cc->glyph->l - dx; |
| double t = cc->y + cc->glyph->t - dy; |
| double r = cc->x + cc->glyph->r + dx; |
| double b = cc->y + cc->glyph->b + dy; |
| |
| double ttx = gh > gw ? gw/gh : 1; |
| double tty = gw > gh ? gh/gw : 1; |
| |
| int gi = cc->glyph - cc->glyph->map->glyphs; |
| double tl = (gi % FONT_MAP_CNT)*texStep - texBorder; |
| double tt = (gi / FONT_MAP_CNT)*texStep - texBorder; |
| double tr = tl + texGlyphSize*ttx + 2*texBorder; |
| double tb = tt + texGlyphSize*tty + 2*texBorder; |
| |
| glTexCoord2d(tl, tt); glVertex2d(l, t); |
| glTexCoord2d(tr, tt); glVertex2d(r, t); |
| glTexCoord2d(tr, tb); glVertex2d(r, b); |
| glTexCoord2d(tl, tb); glVertex2d(l, b); |
| } |
| glEnd(); |
| glBindTexture(GL_TEXTURE_2D, 0); |
| glDisable(GL_TEXTURE_2D); |
| } |
| |
| glPopMatrix(); |
| glDisable(GL_MULTISAMPLE); |
| glColor4d(1, 1, 1, 1); |
| } |
| |
| |
| void textLayoutDrawFrom(TextLayout layout, double x, double y, int start) |
| { textLayoutDrawSubstr(layout, x, y, start, INT_MAX); } |
| void textLayoutDraw(TextLayout layout, double x, double y) |
| { textLayoutDrawFrom(layout, x, y, 0); } |
| |
| |
| void text(double x, double y, const char *text) { |
| resetPath(); |
| |
| if (!text || !*text) return; |
| |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| if (fontScale < HELI_PRECISION) return; |
| if ( drawingState->strokeColor[3] < HELI_PRECISION |
| && drawingState->fillColor[3] < HELI_PRECISION ) return; |
| |
| TextLayout layout = createTextLayout(text); |
| textLayoutDraw(layout, x, y); |
| textLayoutDestroy(layout); |
| } |
| |
| void textf(double x, double y, const char *format, ...) { |
| va_list args; |
| va_start(args, format); |
| |
| va_list args_copy; |
| va_copy(args_copy, args); |
| int size = vsnprintf(NULL, 0, format, args_copy); |
| va_end(args_copy); |
| if (size < 0) size = 0; |
| ++size; |
| char *buffer = calloc(1, size); |
| vsnprintf(buffer, size, format, args); |
| va_end(args); |
| |
| text(x, y, buffer); |
| free(buffer); |
| } |
| |
| |
| double textLayoutGetLeft(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return layout->l*fontScale; |
| } |
| |
| |
| double textLayoutGetTop(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return layout->t*fontScale; |
| } |
| |
| |
| double textLayoutGetWidth(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return (layout->r - layout->l)*fontScale; |
| } |
| |
| |
| double textLayoutGetHeight(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return (layout->b - layout->t)*fontScale; |
| } |
| |
| |
| double textLayoutGetTopAscenderLine(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return (layout->lines->y - layout->lines->xheight)*fontScale; |
| } |
| |
| |
| double textLayoutGetTopXLine(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return (layout->lines->y - layout->lines->ascender)*fontScale; |
| } |
| |
| |
| double textLayoutGetBottomBaseline(TextLayout layout) { |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| return layout->lines[layout->linesCount - 1].y * fontScale; |
| } |
| |
| |
| void heliFontFinish() { |
| defaultFont = NULL; |
| unicodeFont = NULL; |
| heliArrayDestroy(&fontCache); |
| if (ftInitialized && ftLibrary) FT_Done_FreeType(ftLibrary); |
| ftLibrary = NULL; |
| ftInitialized = FALSE; |
| } |
| |
| static void findChar(TextLayout layout, int cursor, int *lineIndex, int *charIndex) { |
| *lineIndex = 0; |
| *charIndex = 0; |
| if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return; |
| |
| *lineIndex = layout->linesCount - 1; |
| *charIndex = layout->lines[*lineIndex].end - 1; |
| for(int i = 0; i < layout->linesCount; ++i) { |
| HeliLineDesc *line = layout->lines + i; |
| if (line->begin < line->end) { |
| if (layout->chars[line->end-1].charpos >= cursor) { |
| *lineIndex = i; |
| *charIndex = line->end-1; |
| for(int j = line->begin; j < line->end; ++j) |
| if (layout->chars[j].charpos >= cursor) |
| { *charIndex = j; break; } |
| return; |
| } |
| } |
| } |
| } |
| |
| static int moveCursorVert(TextLayout layout, int cursor, int offset) { |
| if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0; |
| |
| int li = 0, ci = 0; |
| findChar(layout, cursor, &li, &ci); |
| |
| li += offset; |
| if (li < 0) |
| return 0; |
| if (li >= layout->linesCount) |
| return layout->chars[ layout->lines[layout->linesCount-1].end - 1 ].charpos; |
| |
| double x = layout->chars[ci].x; |
| HeliLineDesc *line = layout->lines + li; |
| for(int j = line->begin; j < line->end; ++j) { |
| HeliCharDesc *cc1 = layout->chars + j; |
| double xx = layout->chars[j].x; |
| if (xx + HELI_PRECISION >= x || j == line->end - 1) { |
| if (j > line->begin) { |
| HeliCharDesc *cc0 = cc1 - 1; |
| if (fabs(cc0->x - x) < fabs(xx - x)) return cc0->charpos; |
| } |
| return cc1->charpos; |
| } |
| } |
| return cursor; |
| } |
| |
| int textLayoutCursorUp(TextLayout layout, int cursor) |
| { return moveCursorVert(layout, cursor, -1); } |
| |
| int textLayoutCursorDown(TextLayout layout, int cursor) |
| { return moveCursorVert(layout, cursor, 1); } |
| |
| double textLayoutCursorGetX(TextLayout layout, int cursor) { |
| if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0; |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| int li = 0, ci = 0; |
| findChar(layout, cursor, &li, &ci); |
| return layout->chars[ci].x * fontScale; |
| } |
| |
| double textLayoutCursorGetY(TextLayout layout, int cursor) { |
| if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0; |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| int li = 0, ci = 0; |
| findChar(layout, cursor, &li, &ci); |
| return layout->chars[ci].y * fontScale; |
| } |
| |
| double textLayoutCursorGetHeight(TextLayout layout, int cursor) { |
| if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0; |
| HeliDrawingState *drawingState = heliDrawingGetState(); |
| double fontScale = drawingState->fontSize/FONT_BASE_SIZE; |
| int li = 0, ci = 0; |
| findChar(layout, cursor, &li, &ci); |
| return layout->lines[li].height * fontScale; |
| } |