/* * Copyright (c) 2001-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author, nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * FreeType 2 module for the AG_Text(3) typography system. */ #include #include #include #include #include #include /* Handy routines for converting from fixed point */ #undef FT_FLOOR #define FT_FLOOR(X) ((X & -64) / 64) #undef FT_CEIL #define FT_CEIL(X) (((X + 63) & -64) / 64) static int agFtInited = 0; /* FreeType library is initalized */ static FT_Library agFtLibrary; /* #define DEBUG_FONTS */ /* * Create and load a FreeType font. * * The name string may include weight/style suffices. * The nameBase string should be the bare font name (without any suffices). * Font names are case-insensitive. * Font names starting with `_' refer to embedded (built-in) fonts. * */ AG_FontFt * AG_FontFtNew(const char *name, const char *nameBase, const AG_FontSpec *spec, const char *path, Uint flags) { AG_FontFt *fontFt; AG_Font *font; if ((fontFt = TryMalloc(sizeof(AG_FontFt))) == NULL) { return (NULL); } font = AGFONT(fontFt); AG_ObjectInit(fontFt, &agFontFtClass); AG_ObjectSetNameS(fontFt, name); Strlcpy(font->name, nameBase, sizeof(font->name)); memcpy(&font->spec, spec, sizeof(AG_FontSpec)); font->flags |= flags; if (AGFONT_OPS(fontFt)->open(fontFt, path) == -1) { AG_ObjectDestroy(fontFt); return (NULL); } return (fontFt); } static int InitFreeType(void) { FT_Error rv; if ((rv = FT_Init_FreeType(&agFtLibrary)) != 0) { AG_SetError("FT_Init_FreeType failed (0x%x)", rv); return (-1); } #ifdef DEBUG_FONTS { FT_Int major, minor, patch; FT_Library_Version(agFtLibrary, &major, &minor, &patch); Debug(NULL, "FreeType " AGSI_BOLD "%d.%d.%d" AGSI_RST " initialized " "(built against %d.%d.%d)\n", major, minor, patch, FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH); } #endif agFtInited++; return (0); } static int Open(void *_Nonnull obj, const char *_Nonnull path) { AG_FontFt *fontFt = obj; AG_Font *font = AGFONT(fontFt); const AG_FontSpec *spec = &font->spec; TT_OS2 *ttOS2; FT_Error rv; FT_Face face; FT_Fixed scale; if (agFtInited == 0) { if (InitFreeType() != 0) return (-1); } agFtInited++; switch (spec->sourceType) { case AG_FONT_SOURCE_FILE: #ifdef DEBUG_FONTS Debug(fontFt, "FT_New_Face(\"" AGSI_PATH "%s" AGSI_RST "\", " AGSI_RED "%.02f" AGSI_RST " pts, " AGSI_RED "#%d" AGSI_RST ")\n", path, spec->size, spec->index); #endif rv = FT_New_Face(agFtLibrary, path, spec->index, &fontFt->face); break; case AG_FONT_SOURCE_MEMORY: #ifdef DEBUG_FONTS Debug(fontFt, "FT_New_Memory_Face(" "%p, %ldKB, " AGSI_RED "%.02f" AGSI_RST " pts, " AGSI_RED "#%d" AGSI_RST ")\n", spec->source.mem.data, (spec->source.mem.size>1024) ? (spec->source.mem.size>>10):1, spec->size, spec->index); #endif rv = FT_New_Memory_Face(agFtLibrary, spec->source.mem.data, spec->source.mem.size, spec->index, &fontFt->face); break; default: rv = 1; break; } if (rv) { AG_SetError("FT_New_Face failed: %x", rv); goto fail; } face = fontFt->face; ttOS2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); if (ttOS2 != NULL) { font->uniRanges[0] = ttOS2->ulUnicodeRange1; font->uniRanges[1] = ttOS2->ulUnicodeRange2; font->uniRanges[2] = ttOS2->ulUnicodeRange3; font->uniRanges[3] = ttOS2->ulUnicodeRange4; } /* Apply the tranformation matrix (if not identity). */ if (spec->matrix.xx != 1.0 || spec->matrix.yy != 1.0 || spec->matrix.xy != 0.0 || spec->matrix.yx != 0.0) { FT_Matrix m; FT_Vector vec; m.xx = spec->matrix.xx * 65536; m.yy = spec->matrix.yy * 65536; m.xy = spec->matrix.xy * 65536; m.yx = spec->matrix.yx * 65536; vec.x = 0.0; vec.y = 0.0; FT_Set_Transform(face, &m, &vec); } if (FT_IS_SCALABLE(face)) { /* Scalable Font */ if ((rv = FT_Set_Char_Size(face, 0, spec->size*64, 0, 0)) != 0) { AG_SetError("FT_Set_Char_Size failed: %x", rv); goto fail_face; } scale = face->size->metrics.y_scale; if (ttOS2) { font->typoAscender = FT_CEIL(FT_MulFix(ttOS2->sTypoAscender, scale)); font->typoDescender = FT_CEIL(FT_MulFix(ttOS2->sTypoDescender, scale)); font->typoLineGap = FT_CEIL(FT_MulFix(ttOS2->sTypoLineGap, scale)); font->usWinAscent = FT_CEIL(FT_MulFix(ttOS2->usWinAscent, scale)); font->usWinDescent = FT_CEIL(FT_MulFix(ttOS2->usWinDescent, scale)); font->ascent = font->typoAscender - font->typoDescender; } else { font->ascent = FT_CEIL(FT_MulFix(face->bbox.yMax, scale)); } font->descent = FT_CEIL(FT_MulFix(face->bbox.yMin, scale)); font->height = font->ascent - font->descent + 1; font->lineskip = FT_CEIL(FT_MulFix(face->height, scale)); font->underlinePos = FT_FLOOR(FT_MulFix(face->underline_position, scale)); font->underlineThk = FT_FLOOR(FT_MulFix(face->underline_thickness, scale)); } else { /* Non-scalable Font */ Uint fixedSize = (Uint)spec->size; if (fixedSize >= face->num_fixed_sizes) { fixedSize = face->num_fixed_sizes - 1; } fontFt->fixedSize = (int)fixedSize; (void)FT_Set_Pixel_Sizes(face, face->available_sizes[fixedSize].height, face->available_sizes[fixedSize].width); /* * With non-scalable fonts, Freetype2 likes to fill many of the * font metrics with 0. The size of the non-scalable fonts must * be determined differently or sometimes cannot be determined. */ font->ascent = face->available_sizes[fixedSize].height - 4; if (font->ascent < 0) { /* TODO remember this correction */ font->ascent = 0; } font->descent = 0; font->height = face->available_sizes[fixedSize].height; font->lineskip = font->height + 4; /* XXX TODO */ font->underlinePos = FT_FLOOR(face->underline_position); font->underlineThk = FT_FLOOR(face->underline_thickness); } if (font->underlineThk < 1) { font->underlineThk = 1; } return (0); fail_face: FT_Done_Face(fontFt->face); fail: return (-1); } static void FlushGlyph(AG_GlyphFt *_Nonnull G) { G->stored = 0; G->index = 0; Free(G->bitmap.buffer); G->bitmap.buffer = NULL; Free(G->pixmap.buffer); G->pixmap.buffer = NULL; G->cached = 0; } /* Flush the entire glyph cache. */ static void FlushCache(void *_Nonnull obj) { AG_FontFt *fontFt = obj; const int size = sizeof(fontFt->cache) / sizeof(fontFt->cache[0]); int i; for (i = 0; i < size; i++) { AG_GlyphFt *G = &fontFt->cache[i]; if (G->cached) FlushGlyph(G); } if (fontFt->scratch.cached) FlushGlyph(&fontFt->scratch); } static void Close(void *_Nonnull obj) { AG_FontFt *fontFt = obj; FlushCache(fontFt); FT_Done_Face(fontFt->face); if (--agFtInited == 0) { FT_Done_FreeType(agFtLibrary); #ifdef DEBUG_FONTS Debug(NULL, "FreeType finalized\n"); #endif } } /* * This special case wouldn't be here if the FT_Render_Glyph() function * wasn't buggy when it tried to render a .fon font with 256 shades of * gray. Instead, it returns a black and white surface and we have to * translate it back to a 256 gray shaded surface. * XXX is this still needed? */ static void ProcessNonScalablePixmap(FT_Bitmap *_Nonnull src, FT_Bitmap *_Nonnull dst, int soffset, int doffset) { Uint8 *srcp, *dstp; Uint8 ch; int j, k; srcp = src->buffer + soffset; dstp = dst->buffer + doffset; for (j = 0; j < src->width; j += 8) { ch = *srcp++; for (k = 0; k < 8; ++k) { if ((ch&0x80) >> 7) { /* * FIXME: Right now we assume the gray-scale * renderer Freetype is using supports 256 * shades of gray, but we should instead key off * of num_grays in the result FT_Bitmap after * the FT_Render_Glyph() call. */ *dstp++ = 0xff; } else { *dstp++ = 0x00; } ch <<= 1; } } } static void * GetGlyph(void *_Nonnull obj, AG_Char ch, Uint want) { AG_FontFt *fontFt = obj; AG_Font *font = AGFONT(fontFt); FT_Face face = fontFt->face; AG_GlyphFt *G; FT_GlyphSlot Gslot; FT_Glyph_Metrics *Gmetrics; FT_Error rv; #ifdef AG_UNICODE if (ch < 256) { #else if (1) { #endif fontFt->current = &fontFt->cache[ch]; } else { if (fontFt->scratch.cached != ch) { FlushGlyph(&fontFt->scratch); } fontFt->current = &fontFt->scratch; } G = fontFt->current; if ((G->stored & want) == want) return (void *)(G); if (G->index == 0) { G->index = FT_Get_Char_Index(face, ch); } if ((rv = FT_Load_Glyph(face, G->index, FT_LOAD_DEFAULT)) != 0) { AG_SetError("FT_Load_Glyph failed (%d)", rv); return (NULL); } /* Get our glyph shortcuts */ Gslot = face->glyph; Gmetrics = &Gslot->metrics; /* * Glyph Metrics. */ if ((want & AG_GLYPH_FT_METRICS) && !(G->stored & AG_GLYPH_FT_METRICS)) { if (FT_IS_SCALABLE(face)) { /* Get the bounding box. */ G->xMin = FT_FLOOR(Gmetrics->horiBearingX); G->xMax = G->xMin + FT_CEIL(Gmetrics->width); G->yMax = FT_FLOOR(Gmetrics->horiBearingY); G->yMin = G->yMax - FT_CEIL(Gmetrics->height); } else { /* * Get the bounding box for non-scalable format. * Again, freetype2 fills in many of the font metrics * with the value of 0, so some of the values we * need must be calculated differently with certain * assumptions about non-scalable formats. */ G->xMin = FT_FLOOR(Gmetrics->horiBearingX); G->xMax = G->xMin + FT_CEIL(Gmetrics->horiAdvance); G->yMax = FT_FLOOR(Gmetrics->horiBearingY); G->yMin = G->yMax - FT_CEIL(face->available_sizes[fontFt->fixedSize].height); } G->yOffset = font->ascent - G->yMax; G->advance = FT_CEIL(Gmetrics->horiAdvance); G->stored |= AG_GLYPH_FT_METRICS; } /* * Rendered two-color bitmap / pixmap. */ if (((want & AG_GLYPH_FT_BITMAP) && !(G->stored & AG_GLYPH_FT_BITMAP)) || ((want & AG_GLYPH_FT_PIXMAP) && !(G->stored & AG_GLYPH_FT_PIXMAP))) { FT_Bitmap *src, *dst; const int wantMono = (want & AG_GLYPH_FT_BITMAP); /* Render the glyph. */ if (FT_Render_Glyph(Gslot, wantMono ? FT_RENDER_MODE_MONO : FT_RENDER_MODE_NORMAL) != 0) { AG_SetError("FT_Render_Glyph(0x%x) failed", (Uint)ch); return (NULL); } /* Copy over information to cache. */ src = &Gslot->bitmap; dst = (wantMono) ? &G->bitmap : &G->pixmap; memcpy(dst, src, sizeof(FT_Bitmap)); /* * FT_Render_Glyph() and .fon fonts always generate a * two-color (black and white) glyph slot surface, even * when rendered in FT_RENDER_MODE_NORMAL. This is probably * a freetype2 bug because it is inconsistent with the * freetype2 documentation under FT_Render_Mode section. */ if (wantMono || !FT_IS_SCALABLE(face)) dst->pitch <<= 3; if (dst->rows != 0) { const AG_Size bufferSize = (dst->pitch * dst->rows); int i; if ((dst->buffer = TryMalloc(bufferSize)) == NULL) { return (NULL); } memset(dst->buffer, 0, bufferSize); for (i = 0; i < src->rows; i++) { const int soffset = i * src->pitch; const int doffset = i * dst->pitch; if (wantMono) { Uint8 *srcp = src->buffer + soffset; Uint8 *dstp = dst->buffer + doffset; int j; for (j = 0; j < src->width; j += 8) { Uint8 ch = *srcp++; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; ch <<= 1; *dstp++ = (ch&0x80) >> 7; } } else if (!FT_IS_SCALABLE(face)) { ProcessNonScalablePixmap(src, dst, soffset, doffset); } else { memcpy(dst->buffer + doffset, src->buffer + soffset, src->pitch); } } } /* Mark that we rendered this format */ if (wantMono) { G->stored |= AG_GLYPH_FT_BITMAP; /* Monochrome */ } else { G->stored |= AG_GLYPH_FT_PIXMAP; /* Gray levels */ } } G->cached = ch; /* Mark this glyph cached */ return (G); } static void GetGlyphMetrics(void *_Nonnull obj, AG_Glyph *G) { AG_GlyphFt *Gft; AG_Font *font = G->font; if ((Gft = GetGlyph(font, G->ch, AG_GLYPH_FT_METRICS)) != NULL) { G->advance = Gft->advance; } else { G->advance = G->su->w; /* Fallback */ } } /* * Calculate the offset in pixels needed to align text based on the * current justification mode. */ static __inline__ int JustifyOffset(const AG_TextState *_Nonnull ts, int w, int wLine) { switch (ts->justify) { case AG_TEXT_LEFT: return (0); case AG_TEXT_CENTER: return ((w >> 1) - (wLine >> 1)); case AG_TEXT_RIGHT: return (w - wLine); } return (0); } static void Render(const AG_Char *_Nonnull ucs, AG_Surface *_Nonnull S, const AG_TextMetrics *_Nonnull Tm, AG_Font *_Nonnull fontOrig, const AG_Color *_Nonnull cBgOrig, const AG_Color *_Nonnull cFgOrig) { const AG_TextState *ts = AG_TEXT_STATE_CUR(); AG_Font *fontCur = fontOrig; AG_FontFt *fontFtCur; const AG_Char *ch; Uint8 *src, *dst; AG_Color cBg = *cBgOrig; AG_Color cFg = *cFgOrig; FT_UInt indexPrev = 0; const int BytesPerPixel = S->format.BytesPerPixel; const int lineskip = fontOrig->lineskip; int xStart, yStart, line, x,y, w; int ascentCur; xStart = (Tm->nLines>1) ? JustifyOffset(ts, Tm->w, Tm->wLines[0]) : 0; yStart = 0; ascentCur = fontCur->ascent; S->guides[0] = (Uint16)(fontCur->height - ascentCur); for (ch = &ucs[0], line=0; *ch != '\0'; ch++) { switch (*ch) { case '\n': yStart += lineskip; xStart = JustifyOffset(ts, Tm->w, Tm->wLines[++line]); continue; case '\r': xStart = 0; continue; case '\t': xStart += ts->tabWd; /* TODO */ continue; default: break; } if (ch[0] == 0x1b && ch[1] >= 0x40 && ch[1] <= 0x5f && ch[2] != '\0') { AG_TextANSI ansi; if (AG_TextParseANSI(ts, &ansi, &ch[1]) != 0) { /* AG_Verbose("%s; ignoring\n", AG_GetError()); */ continue; } if (ansi.ctrl != AG_ANSI_CSI_SGR) { ch += ansi.len; continue; } switch (ansi.sgr) { case AG_SGR_RESET: case AG_SGR_NO_FG_NO_BG: fontCur = fontOrig; ascentCur = fontCur->ascent; cFg = *cFgOrig; cBg = *cBgOrig; break; case AG_SGR_FG: cFg = ansi.color; break; case AG_SGR_BG: cBg = ansi.color; break; case AG_SGR_BOLD: fontCur = AG_FetchFont(fontCur->name, fontCur->spec.size, fontCur->flags | AG_FONT_BOLD); if (fontCur == NULL) { fontCur = fontOrig; } ascentCur = MAX(fontOrig->ascent, fontCur->ascent); break; case AG_SGR_ITALIC: fontCur = AG_FetchFont(fontCur->name, fontCur->spec.size, fontCur->flags | AG_FONT_ITALIC); if (fontCur == NULL) { fontCur = fontOrig; } ascentCur = MAX(fontOrig->ascent, fontCur->ascent); break; case AG_SGR_FAINT: /* TODO */ break; case AG_SGR_UNDERLINE: /* TODO */ break; case AG_SGR_PRI_FONT: case AG_SGR_ALT_FONT_1: case AG_SGR_ALT_FONT_2: case AG_SGR_ALT_FONT_3: case AG_SGR_ALT_FONT_4: case AG_SGR_ALT_FONT_5: case AG_SGR_ALT_FONT_6: case AG_SGR_ALT_FONT_7: case AG_SGR_ALT_FONT_8: case AG_SGR_ALT_FONT_9: case AG_SGR_FRAKTUR: fontCur = AG_FetchFont( agCoreFonts[ansi.sgr - 10], fontCur->spec.size, fontCur->flags); if (fontCur == NULL) { fontCur = fontOrig; } ascentCur = MAX(fontOrig->ascent, fontCur->ascent); break; case AG_SGR_ALT_FONT_11: case AG_SGR_ALT_FONT_12: case AG_SGR_ALT_FONT_13: case AG_SGR_ALT_FONT_14: case AG_SGR_ALT_FONT_15: case AG_SGR_ALT_FONT_16: case AG_SGR_ALT_FONT_17: fontCur = AG_FetchFont( agCoreFonts[11 + (ansi.sgr - 66)], fontOrig->spec.size, fontOrig->flags); if (fontCur == NULL) { fontCur = fontOrig; } ascentCur = MAX(fontOrig->ascent, fontCur->ascent); break; default: break; } ch += ansi.len; continue; } if (fontCur->spec.type == AG_FONT_FREETYPE) { AG_GlyphFt *G; int yOffset; fontFtCur = AGFONTFT(fontCur); G = AGFONT_OPS(fontCur)->get_glyph(fontCur, *ch, AG_GLYPH_FT_METRICS | AG_GLYPH_FT_PIXMAP); if (G == NULL) { /* TODO render a blank box and continue */ return; } yOffset = ascentCur - G->yMax; /* * Ensure the width of the pixmap is correct. On some cases, * freetype may report a larger pixmap than possible. */ if ((w = G->pixmap.width) > G->xMax - G->xMin) w = G->xMax - G->xMin; if (FT_HAS_KERNING(fontFtCur->face) && indexPrev && G->index) { FT_Vector delta; FT_Get_Kerning(fontFtCur->face, indexPrev, G->index, FT_KERNING_DEFAULT, &delta); xStart += delta.x >> 6; } /* Prevent texture wrapping with first glyph. */ if ((ch == &ucs[0]) && (G->xMin < 0)) { xStart -= G->xMin; } if ((xStart + G->xMin) < 0 || (xStart + G->xMin) >= S->w) { continue; } if (cBg.a == AG_TRANSPARENT) { /* * Lower blit - Write on transparent BG. */ for (y = 0; y < G->pixmap.rows; y++) { if ((yStart + y + yOffset) < 0 || (yStart + y + yOffset) >= S->h) continue; src = (Uint8 *)(G->pixmap.buffer + G->pixmap.pitch * y); dst = S->pixels + (yStart + y + yOffset) * S->pitch + (xStart + G->xMin) * BytesPerPixel; for (x = 0; x < w; x++) { if ((cFg.a = AG_8toH(*src++)) > 0) { AG_SurfacePut_At(S, dst, AG_MapPixel(&S->format, &cFg)); } dst += BytesPerPixel; } } } else { /* * Lower blit - Blend against colored BG. */ for (y = 0; y < G->pixmap.rows; y++) { if (y + yOffset < 0 || y + yOffset >= S->h) continue; src = (Uint8 *)(G->pixmap.buffer + G->pixmap.pitch * y); dst = S->pixels + (yStart + y + yOffset) * S->pitch + (xStart + G->xMin) * BytesPerPixel; for (x = 0; x < w; x++) { if ((cFg.a = AG_8toH(*src++)) > 0) { AG_SurfaceBlend_At(S, dst, &cFg); } dst += BytesPerPixel; } } } xStart += G->advance; indexPrev = G->index; } else if (fontCur->spec.type == AG_FONT_BITMAP) { AG_FontBf *fontBfCur = AGFONTBF(fontCur); AG_GlyphBf *Gbf; Gbf = AGFONT_OPS(fontBfCur)->get_glyph(fontBfCur, *ch, 0); if (Gbf == NULL) { #ifdef DEBUG_FONTS Debug(fontBfCur, "Not found: `%c' (0x%x)\n", (char)*ch, *ch); #endif xStart += fontBfCur->wdRef; continue; } (void)cBg; (void)cFg; if (*ch != ' ') { AG_SurfaceBlit(fontBfCur->S, &Gbf->rs, S, xStart, yStart + ascentCur - Gbf->rs.h + Gbf->yOffset); } xStart += Gbf->rs.w + fontBfCur->advance; } } } static void Size(const AG_Font *_Nonnull font, const AG_Char *_Nonnull ucs, AG_TextMetrics *_Nonnull Tm, int extended) { AG_TextState *ts = AG_TEXT_STATE_CUR(); AG_Font *fontOrig = ts->font, *fontCur = fontOrig; const AG_Char *ch; const int lineskip = fontCur->lineskip; int xMin=0, xMax=0, yMin=0, yMax; int xMinLine=0, xMaxLine=0; int x, z; /* Compute the sum of the bounding box of the characters. */ yMax = fontCur->lineskip; x = 0; for (ch = &ucs[0]; *ch != '\0'; ch++) { if (*ch == '\n') { if (extended) { Tm->wLines = Realloc(Tm->wLines, (Tm->nLines + 2) * sizeof(Uint)); Tm->wLines[Tm->nLines++] = (xMaxLine - xMinLine); xMinLine = 0; xMaxLine = 0; } yMax += lineskip; x = 0; continue; } if (*ch == '\t') { x += ts->tabWd; /* XXX TODO */ continue; } if (ch[0] == 0x1b && ch[1] >= 0x40 && ch[1] <= 0x5f && ch[2] != '\0') { AG_TextANSI ansi; if (AG_TextParseANSI(ts, &ansi, &ch[1]) != 0) { continue; } if (ansi.ctrl != AG_ANSI_CSI_SGR) { ch += ansi.len; continue; } switch (ansi.sgr) { case AG_SGR_RESET: case AG_SGR_NO_FG_NO_BG: fontCur = fontOrig; break; case AG_SGR_BOLD: fontCur = AG_FetchFont(fontOrig->name, fontOrig->spec.size, fontOrig->flags | AG_FONT_BOLD); if (fontCur == NULL) { fontCur = fontOrig; } break; case AG_SGR_ITALIC: fontCur = AG_FetchFont(fontOrig->name, fontOrig->spec.size, fontOrig->flags | AG_FONT_ITALIC); if (fontCur == NULL) { fontCur = fontOrig; } break; case AG_SGR_PRI_FONT: case AG_SGR_ALT_FONT_1: case AG_SGR_ALT_FONT_2: case AG_SGR_ALT_FONT_3: case AG_SGR_ALT_FONT_4: case AG_SGR_ALT_FONT_5: case AG_SGR_ALT_FONT_6: case AG_SGR_ALT_FONT_7: case AG_SGR_ALT_FONT_8: case AG_SGR_ALT_FONT_9: case AG_SGR_FRAKTUR: fontCur = AG_FetchFont( agCoreFonts[ansi.sgr - 10], fontCur->spec.size, fontCur->flags); if (fontCur == NULL) { fontCur = fontOrig; } break; case AG_SGR_ALT_FONT_11: case AG_SGR_ALT_FONT_12: case AG_SGR_ALT_FONT_13: case AG_SGR_ALT_FONT_14: case AG_SGR_ALT_FONT_15: case AG_SGR_ALT_FONT_16: case AG_SGR_ALT_FONT_17: fontCur = AG_FetchFont( agCoreFonts[11 + (ansi.sgr - 66)], fontCur->spec.size, fontCur->flags); if (fontCur == NULL) { fontCur = fontOrig; } break; default: break; } ch += ansi.len; continue; } if (fontCur->spec.type == AG_FONT_FREETYPE) { AG_GlyphFt *Gft; Gft = AGFONT_OPS(fontCur)->get_glyph(fontCur, *ch, AG_GLYPH_FT_METRICS); z = x + Gft->xMin; if (xMin > z) { xMin = z; } if (xMinLine > z) { xMinLine = z; } z = x + MAX(Gft->advance, Gft->xMax); if (xMax < z) { xMax = z; } if (xMaxLine < z) { xMaxLine = z; } x += Gft->advance; if (Gft->yMin < yMin) { yMin = Gft->yMin; } if (Gft->yMax > yMax) { yMax = Gft->yMax; } } else if (fontCur->spec.type == AG_FONT_BITMAP) { AG_GlyphBf *Gbf; int advance; Gbf = AGFONT_OPS(fontCur)->get_glyph(fontCur, *ch, 0); if (Gbf == NULL) { x += AGFONTBF(fontCur)->wdRef; continue; } if (xMin > x) { xMin = x; } if (xMinLine > x) { xMinLine = x; } advance = AGFONTBF(fontCur)->advance; z = x + Gbf->rs.w + advance; if (xMax < z) { xMax = z; } if (xMaxLine < z) { xMaxLine = z; } x += Gbf->rs.w + advance; } } if (*ch != '\n' && extended) { if (Tm->nLines > 0) { Tm->wLines = Realloc(Tm->wLines, (Tm->nLines + 2) * sizeof(Uint)); Tm->wLines[Tm->nLines] = (xMaxLine - xMinLine); } Tm->nLines++; } Tm->w = (xMax - xMin) + 1; Tm->h = (yMax - yMin) + 1; } static void Init(void *_Nonnull obj) { AG_FontFt *font = obj; memset(&font->current, 0, sizeof(AG_GlyphFt *) + /* current */ sizeof(AG_GlyphFt)*256 + /* cache */ sizeof(AG_GlyphFt) + /* scratch */ sizeof(int)); /* fixedSize */ } AG_FontClass agFontFtClass = { { "Agar(Font:FontFt)", sizeof(AG_FontFt), { 1,0, AGC_FONT_FT, 0xE01B }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Open, FlushCache, Close, GetGlyph, GetGlyphMetrics, Render, Size };