| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / FreeType font engine module | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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 Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/modules/font.h> | |
| #include <gpac/list.h> | |
| #include <gpac/utf.h> | |
| #include <gpac/tools.h> | |
| #ifndef GPAC_DISABLE_EVG | |
| #if !defined(__GNUC__) | |
| # if defined(_WIN32_WCE) | |
| # pragma comment(lib, "freetype") | |
| # elif defined (WIN32) | |
| # pragma comment(lib, "freetype") | |
| # endif | |
| #endif | |
| #include <ft2build.h> | |
| #include FT_FREETYPE_H | |
| #include FT_GLYPH_H | |
| #include FT_OUTLINE_H | |
| /*TrueType tables*/ | |
| #include FT_TRUETYPE_TABLES_H | |
| typedef struct | |
| { | |
| FT_Library library; | |
| FT_Face active_face; | |
| GF_List *font_dirs; | |
| GF_List *loaded_fonts; | |
| /*default fonts*/ | |
| char *font_serif, *font_sans, *font_fixed, *font_default; | |
| Bool cache_checked; | |
| } FTBuilder; | |
| static const char * BEST_FIXED_FONTS[] = { | |
| "Courier New", | |
| "Courier", | |
| "Monaco", | |
| "Bitstream Vera Monospace", | |
| "Droid Sans Mono", | |
| NULL | |
| }; | |
| static const char * BEST_SERIF_FONTS[] = { | |
| "Times New Roman", | |
| "Bitstream Vera", | |
| "Times", | |
| "Droid Serif", | |
| NULL | |
| }; | |
| static const char * BEST_SANS_FONTS[] = { | |
| "Arial", | |
| "Tahoma", | |
| "Verdana", | |
| "Helvetica", | |
| "Bitstream Vera Sans", | |
| "Frutiger", | |
| "Droid Sans", | |
| NULL | |
| }; | |
| /** | |
| * Choose the best font in the list of fonts | |
| */ | |
| static Bool isBestFontFor(const char * listOfFonts[], const char * currentBestFont, const char * fontName) { | |
| u32 i; | |
| for (i = 0 ; listOfFonts[i]; i++) { | |
| const char * best = listOfFonts[i]; | |
| if (!stricmp(best, fontName)) | |
| return GF_TRUE; | |
| if (currentBestFont && !stricmp(best, currentBestFont)) | |
| return GF_FALSE; | |
| } | |
| /* Nothing has been found, the font is the best if none has been choosen before */ | |
| return currentBestFont == NULL; | |
| } | |
| void setBestFont(const char * listOfFonts[], char ** currentBestFont, const char * fontName) { | |
| if (isBestFontFor(listOfFonts, *currentBestFont, fontName)) { | |
| if (*currentBestFont) | |
| gf_free(*currentBestFont); | |
| *currentBestFont = NULL; | |
| } | |
| if (! (*currentBestFont)) { | |
| *currentBestFont = gf_strdup(fontName); | |
| } | |
| } | |
| static Bool ft_enum_fonts(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) | |
| { | |
| char *szfont; | |
| FT_Face face; | |
| u32 num_faces, i; | |
| GF_FontReader *dr = cbck; | |
| FTBuilder *ftpriv = dr->udta; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Enumerating font %s (%s)\n", file_name, file_path)); | |
| if (FT_New_Face(ftpriv->library, file_path, 0, & face )) return 0; | |
| if (!face || !face->family_name) return 0; | |
| num_faces = (u32) face->num_faces; | |
| /*locate right font in collection if several*/ | |
| for (i=0; i<num_faces; i++) { | |
| /*only scan scalable fonts*/ | |
| if (face->face_flags & FT_FACE_FLAG_SCALABLE) { | |
| Bool bold, italic; | |
| szfont = gf_strdup(face->family_name); | |
| if (!szfont) continue; | |
| bold = italic = 0; | |
| if (face->style_name) { | |
| char *name = gf_strdup(face->style_name); | |
| strupr(name); | |
| if (strstr(name, "BOLD")) bold = 1; | |
| if (strstr(name, "ITALIC")) italic = 1; | |
| /*if font is not regular style, append all styles blindly*/ | |
| if (!strstr(name, "REGULAR")) { | |
| gf_dynstrcat(&szfont, " ", NULL); | |
| gf_dynstrcat(&szfont, face->style_name, NULL); | |
| } | |
| gf_free(name); | |
| } else { | |
| if (face->style_flags & FT_STYLE_FLAG_BOLD) bold = 1; | |
| if (face->style_flags & FT_STYLE_FLAG_ITALIC) italic = 1; | |
| if (bold) gf_dynstrcat(&szfont, " Bold", NULL); | |
| if (italic) gf_dynstrcat(&szfont, " Italic", NULL); | |
| } | |
| gf_opts_set_key("FontCache", szfont, file_path); | |
| /*remember first font found which looks like a alphabetical one*/ | |
| if (!ftpriv->font_default) { | |
| u32 gidx; | |
| FT_Select_Charmap(face, FT_ENCODING_UNICODE); | |
| gidx = FT_Get_Char_Index(face, (u32) 'a'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) 'z'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) '1'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) '@'); | |
| if (gidx) ftpriv->font_default = gf_strdup(szfont); | |
| } | |
| /*try to assign default fixed fonts*/ | |
| if (!bold && !italic) { | |
| /* if a bold or italic default has been set, prefer a regular one */ | |
| if (ftpriv->font_default && (strstr(ftpriv->font_default, " Bold") || strstr(ftpriv->font_default, " Italic"))) { | |
| u32 gidx; | |
| FT_Select_Charmap(face, FT_ENCODING_UNICODE); | |
| gidx = FT_Get_Char_Index(face, (u32) 'a'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) 'z'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) '1'); | |
| if (gidx) gidx = FT_Get_Char_Index(face, (u32) '@'); | |
| if (gidx) { | |
| gf_free(ftpriv->font_default); | |
| ftpriv->font_default = gf_strdup(szfont); | |
| } | |
| } | |
| if (szfont) gf_free(szfont); | |
| szfont = gf_strdup(face->family_name); | |
| strlwr(szfont); | |
| if (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { | |
| setBestFont(BEST_FIXED_FONTS, &(ftpriv->font_fixed), face->family_name); | |
| } | |
| setBestFont(BEST_SERIF_FONTS, &(ftpriv->font_serif), face->family_name); | |
| setBestFont(BEST_SANS_FONTS, &(ftpriv->font_sans), face->family_name); | |
| } | |
| gf_free(szfont); | |
| } | |
| FT_Done_Face(face); | |
| if (i+1==num_faces) return 0; | |
| /*load next font in collection*/ | |
| if (FT_New_Face(ftpriv->library, file_path, i+1, & face )) return 0; | |
| if (!face) return 0; | |
| } | |
| return 0; | |
| } | |
| static Bool ft_enum_fonts_dir(void *cbck, char *file_name, char *file_path, GF_FileEnumInfo *file_info) | |
| { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Scanning directory %s (%s)\n", file_name, file_path)); | |
| gf_enum_directory(file_path, 0, ft_enum_fonts, cbck, "ttf;ttc"); | |
| return (gf_enum_directory(file_path, 1, ft_enum_fonts_dir, cbck, NULL)==GF_OK) ? GF_FALSE : GF_TRUE; | |
| } | |
| static void ft_rescan_fonts(GF_FontReader *dr) | |
| { | |
| u32 i, count; | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| ftpriv->cache_checked = GF_TRUE; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Rescaning %d font directories\n", gf_list_count(ftpriv->font_dirs) )); | |
| gf_opts_del_section("FontCache"); | |
| gf_opts_del_section("temp_freetype"); | |
| gf_opts_set_key("core", "rescan-fonts", "no"); | |
| if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); | |
| ftpriv->font_fixed = NULL; | |
| if (ftpriv->font_sans) gf_free(ftpriv->font_sans); | |
| ftpriv->font_sans = NULL; | |
| if (ftpriv->font_serif) gf_free(ftpriv->font_serif); | |
| ftpriv->font_serif = NULL; | |
| if (ftpriv->font_default) gf_free(ftpriv->font_default); | |
| ftpriv->font_default = NULL; | |
| count = gf_list_count(ftpriv->font_dirs); | |
| for (i=0; i<count; i++) { | |
| char *font_dir = gf_list_get(ftpriv->font_dirs, i); | |
| if (gf_dir_exists(font_dir)) { | |
| gf_enum_directory(font_dir, 0, ft_enum_fonts, dr, "ttf;ttc"); | |
| gf_enum_directory(font_dir, 1, ft_enum_fonts_dir, dr, NULL); | |
| } | |
| } | |
| if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); | |
| ftpriv->font_fixed = NULL; | |
| if (ftpriv->font_sans) gf_free(ftpriv->font_sans); | |
| ftpriv->font_sans = NULL; | |
| if (ftpriv->font_serif) gf_free(ftpriv->font_serif); | |
| ftpriv->font_serif = NULL; | |
| /* let's check we have fonts that match our default Bold/Italic/BoldItalic conventions*/ | |
| count = gf_opts_get_key_count("FontCache"); | |
| for (i=0; i<count; i++) { | |
| const char *opt; | |
| char fkey[GF_MAX_PATH]; | |
| const char *key = gf_opts_get_key_name("FontCache", i); | |
| if (!key) continue; | |
| opt = gf_opts_get_key("FontCache", key); | |
| if (!strchr(opt, '/') && !strchr(opt, '\\')) continue; | |
| if (strstr(key, "Bold")) continue; | |
| if (strstr(key, "Italic")) continue; | |
| gf_strcpy(fkey, key); | |
| gf_strcat(fkey, " Italic"); | |
| opt = gf_opts_get_key("FontCache", fkey); | |
| if (!opt) continue; | |
| gf_strcpy(fkey, key); | |
| gf_strcat(fkey, " Bold"); | |
| opt = gf_opts_get_key("FontCache", fkey); | |
| if (!opt) continue; | |
| gf_strcpy(fkey, key); | |
| gf_strcat(fkey, " Bold Italic"); | |
| opt = gf_opts_get_key("FontCache", fkey); | |
| if (!opt) continue; | |
| gf_strcpy(fkey, key); | |
| strlwr(fkey); | |
| /*this font is suited for our case*/ | |
| if (isBestFontFor(BEST_FIXED_FONTS, ftpriv->font_fixed, key) || (!ftpriv->font_fixed && (strstr(fkey, "fixed") || strstr(fkey, "mono")) ) ) { | |
| if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); | |
| ftpriv->font_fixed = gf_strdup(key); | |
| } | |
| if (isBestFontFor(BEST_SANS_FONTS, ftpriv->font_sans, key) || (!ftpriv->font_sans && strstr(fkey, "sans")) ) { | |
| if (ftpriv->font_sans) gf_free(ftpriv->font_sans); | |
| ftpriv->font_sans = gf_strdup(key); | |
| } | |
| if (isBestFontFor(BEST_SERIF_FONTS, ftpriv->font_serif, key) || (!ftpriv->font_serif && strstr(fkey, "serif")) ) { | |
| if (ftpriv->font_serif) gf_free(ftpriv->font_serif); | |
| ftpriv->font_serif = gf_strdup(key); | |
| } | |
| } | |
| if (!ftpriv->font_serif) ftpriv->font_serif = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); | |
| if (!ftpriv->font_sans) ftpriv->font_sans = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); | |
| if (!ftpriv->font_fixed) ftpriv->font_fixed = gf_strdup(ftpriv->font_default ? ftpriv->font_default : ""); | |
| gf_opts_set_key("FontCache", "FontFixed", ftpriv->font_fixed); | |
| gf_opts_set_key("FontCache", "FontSerif", ftpriv->font_serif); | |
| gf_opts_set_key("FontCache", "FontSans", ftpriv->font_sans); | |
| gf_opts_save(); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Font directories scanned - defaults: SANS %s FIXED %s SERIF %s\n", ftpriv->font_sans, ftpriv->font_fixed, ftpriv->font_serif)); | |
| } | |
| static GF_Err ft_init_font_engine(GF_FontReader *dr) | |
| { | |
| const char *sOpt; | |
| u32 rescan = 0; | |
| GF_Err e; | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| /*inits freetype*/ | |
| if (FT_Init_FreeType(&ftpriv->library) ) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[FreeType] Cannot initialize FreeType\n")); | |
| return GF_IO_ERR; | |
| } | |
| if (!gf_opts_get_key_count("FontCache")) | |
| rescan = 1; | |
| else { | |
| sOpt = gf_opts_get_key("core", "rescan-fonts"); | |
| if (!sOpt || !strcmp(sOpt, "yes") ) | |
| rescan = 1; | |
| } | |
| rescan_font_dirs: | |
| sOpt = gf_opts_get_key("core", "font-dirs"); | |
| if (!sOpt) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[FreeType] No fonts directory indicated!")); | |
| return GF_BAD_PARAM; | |
| } | |
| while (sOpt) { | |
| char dir[GF_MAX_PATH]; | |
| char *sep = (char *) strchr(sOpt, ','); | |
| if (sep) sep[0] = 0; | |
| gf_strcpy(dir, sOpt); | |
| while ( (dir[strlen(dir)-1] == '\n') || (dir[strlen(dir)-1] == '\r') ) | |
| dir[strlen(dir)-1] = 0; | |
| if (dir[strlen(dir)-1] != GF_PATH_SEPARATOR) { | |
| char ext[2]; | |
| ext[0] = GF_PATH_SEPARATOR; | |
| ext[1] = 0; | |
| gf_strcat(dir, ext); | |
| } | |
| gf_list_add(ftpriv->font_dirs, gf_strdup(dir) ); | |
| if (!sep) break; | |
| sep[0] = ','; | |
| sOpt = sep+1; | |
| } | |
| rescan_fonts: | |
| if (rescan) | |
| ft_rescan_fonts(dr); | |
| if (!ftpriv->font_serif) { | |
| sOpt = gf_opts_get_key("FontCache", "FontSerif"); | |
| ftpriv->font_serif = gf_strdup(sOpt ? sOpt : ""); | |
| } | |
| if (!ftpriv->font_sans) { | |
| sOpt = gf_opts_get_key("FontCache", "FontSans"); | |
| ftpriv->font_sans = gf_strdup(sOpt ? sOpt : ""); | |
| } | |
| if (!ftpriv->font_fixed) { | |
| sOpt = gf_opts_get_key("FontCache", "FontFixed"); | |
| ftpriv->font_fixed = gf_strdup(sOpt ? sOpt : ""); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_MODULE, ("[FreeType] Init OK - %d font directory (first %s)\n", gf_list_count(ftpriv->font_dirs), gf_list_get(ftpriv->font_dirs, 0) )); | |
| e = dr->set_font(dr, ftpriv->font_serif, 0); | |
| if (!e) e = dr->set_font(dr, ftpriv->font_sans, 0); | |
| if (!e) e = dr->set_font(dr, ftpriv->font_fixed, 0); | |
| if (!e) return GF_OK; | |
| if (rescan==3) | |
| return e; | |
| if (ftpriv->font_serif) gf_free(ftpriv->font_serif); | |
| ftpriv->font_serif = NULL; | |
| if (ftpriv->font_sans) gf_free(ftpriv->font_sans); | |
| ftpriv->font_sans = NULL; | |
| if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); | |
| ftpriv->font_fixed = NULL; | |
| //error and we rescanned font dirs, restore default fonts | |
| if (e && ftpriv->cache_checked) | |
| rescan = 2; | |
| if (!rescan) { | |
| sOpt = gf_opts_get_key("core", "font-dirs"); | |
| rescan = 2; | |
| if (sOpt) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] Default fonts not valid, rescanning font directories %s\n", sOpt)); | |
| goto rescan_fonts; | |
| } | |
| } | |
| if (rescan==2) { | |
| void gf_get_default_font_dir(char szPath[GF_MAX_PATH]); | |
| char szPath[GF_MAX_PATH]; | |
| //check if font directory is default one, if not reset and rescan | |
| gf_get_default_font_dir(szPath); | |
| sOpt = gf_opts_get_key("core", "font-dirs"); | |
| if (sOpt && !strcmp(sOpt, szPath)) | |
| return e; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] No fonts found in %s, restoring default directories %s and rescanning\n", sOpt, szPath)); | |
| gf_opts_set_key("core", "font-dirs", szPath); | |
| rescan = 3; | |
| while (gf_list_count(ftpriv->font_dirs)) { | |
| gf_free(gf_list_pop_back(ftpriv->font_dirs)); | |
| } | |
| goto rescan_font_dirs; | |
| } | |
| return GF_OK; | |
| } | |
| static GF_Err ft_shutdown_font_engine(GF_FontReader *dr) | |
| { | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| ftpriv->active_face = NULL; | |
| /*reset loaded fonts*/ | |
| while (gf_list_count(ftpriv->loaded_fonts)) { | |
| FT_Face face = gf_list_pop_front(ftpriv->loaded_fonts); | |
| FT_Done_Face(face); | |
| } | |
| /*exit FT*/ | |
| if (ftpriv->library) FT_Done_FreeType(ftpriv->library); | |
| ftpriv->library = NULL; | |
| return GF_OK; | |
| } | |
| static Bool ft_check_face(FT_Face font, const char *fontName, u32 styles) | |
| { | |
| u32 ft_style, loc_styles; | |
| char *name; | |
| if (fontName && stricmp(font->family_name, fontName)) return 0; | |
| ft_style = 0; | |
| if (font->style_name) { | |
| name = gf_strdup(font->style_name); | |
| strupr(name); | |
| if (strstr(name, "BOLD")) ft_style |= GF_FONT_WEIGHT_BOLD; | |
| if (strstr(name, "ITALIC")) ft_style |= GF_FONT_ITALIC; | |
| gf_free(name); | |
| } else { | |
| if (font->style_flags & FT_STYLE_FLAG_BOLD) ft_style |= GF_FONT_WEIGHT_BOLD; | |
| if (font->style_flags & FT_STYLE_FLAG_ITALIC) ft_style |= GF_FONT_ITALIC; | |
| } | |
| name = gf_strdup(font->family_name); | |
| strupr(name); | |
| if (strstr(name, "BOLD")) ft_style |= GF_FONT_WEIGHT_BOLD; | |
| if (strstr(name, "ITALIC")) ft_style |= GF_FONT_ITALIC; | |
| gf_free(name); | |
| loc_styles = styles & GF_FONT_WEIGHT_MASK; | |
| if (loc_styles>=GF_FONT_WEIGHT_BOLD) | |
| styles = (styles & 0x00000007) | GF_FONT_WEIGHT_BOLD; | |
| else | |
| styles = (styles & 0x00000007); | |
| if (ft_style==styles) | |
| return 1; | |
| return 0; | |
| } | |
| static FT_Face ft_font_in_cache(FTBuilder *ft, const char *fontName, u32 styles) | |
| { | |
| u32 i=0; | |
| FT_Face font; | |
| while ((font = gf_list_enum(ft->loaded_fonts, &i))) { | |
| if (ft_check_face(font, fontName, styles)) return font; | |
| } | |
| return NULL; | |
| } | |
| static GF_Err ft_set_font(GF_FontReader *dr, const char *OrigFontName, u32 styles) | |
| { | |
| GF_Err e = GF_OK; | |
| char *fname=NULL; | |
| char *fontName; | |
| const char *opt; | |
| Bool is_def_font = GF_FALSE; | |
| Bool no_style_check = GF_FALSE; | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| fontName = (char *) OrigFontName; | |
| ftpriv->active_face = NULL; | |
| opt = gf_opts_get_key("temp_freetype", OrigFontName); | |
| if (opt) return GF_NOT_SUPPORTED; | |
| if (!fontName || !strlen(fontName) || !stricmp(fontName, "SERIF")) { | |
| fontName = ftpriv->font_serif; | |
| is_def_font = GF_TRUE; | |
| OrigFontName = ""; | |
| } | |
| else if (!stricmp(fontName, "SANS") || !stricmp(fontName, "sans-serif")) { | |
| fontName = ftpriv->font_sans; | |
| is_def_font = GF_TRUE; | |
| OrigFontName = "SANS"; | |
| } | |
| else if (!stricmp(fontName, "TYPEWRITER") || !stricmp(fontName, "monospace")) { | |
| fontName = ftpriv->font_fixed; | |
| is_def_font = GF_TRUE; | |
| OrigFontName = "TYPEWRITER"; | |
| } | |
| if (!styles) { | |
| if (ftpriv->font_fixed && !strcmp(fontName, ftpriv->font_fixed)) no_style_check = GF_TRUE; | |
| else if (ftpriv->font_sans && !strcmp(fontName, ftpriv->font_sans)) no_style_check = GF_TRUE; | |
| else if (ftpriv->font_serif && !strcmp(fontName, ftpriv->font_serif)) no_style_check = GF_TRUE; | |
| } | |
| /*first look in loaded fonts*/ | |
| ftpriv->active_face = ft_font_in_cache(ftpriv, fontName, styles); | |
| if (ftpriv->active_face) return GF_OK; | |
| //we likely have a problem with the font cache, rebuild if | |
| if (!fontName || !strlen(fontName)) { | |
| if (is_def_font && !ftpriv->cache_checked) { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] No default font set, rescanning fonts\n")); | |
| ft_rescan_fonts(dr); | |
| return ft_set_font(dr, OrigFontName, styles); | |
| } | |
| return GF_NOT_SUPPORTED; | |
| } | |
| int checkStyles = (styles & GF_FONT_WEIGHT_BOLD) | (styles & GF_FONT_ITALIC); | |
| checkFont: | |
| if (fname) gf_free(fname); | |
| fname = gf_strdup(fontName); | |
| if (checkStyles & GF_FONT_WEIGHT_BOLD) gf_dynstrcat(&fname, " Bold", NULL); | |
| if (checkStyles & GF_FONT_ITALIC) gf_dynstrcat(&fname, " Italic", NULL); | |
| opt = gf_opts_get_key("FontCache", fname); | |
| if (opt) { | |
| u32 i, num_faces; | |
| FT_Face face; | |
| //check font is in cache | |
| ftpriv->active_face = ft_font_in_cache(ftpriv, fname, checkStyles); | |
| if (ftpriv->active_face) { | |
| gf_free(fname); | |
| return GF_OK; | |
| } | |
| //load face | |
| if (FT_New_Face(ftpriv->library, opt, 0, & face) || !face) { | |
| e = GF_IO_ERR; | |
| goto exit; | |
| } | |
| //handle collections, figure out which face matches our styles | |
| num_faces = face->num_faces; | |
| for (i=0; i<num_faces; i++) { | |
| if (no_style_check || ft_check_face(face, NULL, checkStyles)) { | |
| gf_free(fname); | |
| gf_list_add(ftpriv->loaded_fonts, face); | |
| ftpriv->active_face = face; | |
| return GF_OK; | |
| } | |
| FT_Done_Face(face); | |
| if (i+1==num_faces) break; | |
| /*load next font in collection*/ | |
| if (FT_New_Face(ftpriv->library, opt, i+1, & face )) | |
| break; | |
| if (!face) | |
| break; | |
| } | |
| } | |
| if (checkStyles) { | |
| //we tried bold italic, try bold | |
| if (checkStyles == (GF_FONT_WEIGHT_BOLD | GF_FONT_ITALIC)) | |
| checkStyles = GF_FONT_WEIGHT_BOLD; | |
| //we tried bold at previous pass, and input styles had italic, try italic | |
| else if ((checkStyles == GF_FONT_WEIGHT_BOLD) && (styles & GF_FONT_ITALIC)) | |
| checkStyles = GF_FONT_ITALIC; | |
| //otherwise try with no style | |
| else if ((checkStyles == GF_FONT_WEIGHT_BOLD) || (checkStyles == GF_FONT_ITALIC)) | |
| checkStyles = 0; | |
| goto checkFont; | |
| } | |
| exit: | |
| if (e) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MODULE, ("[FreeType] Error loading font %s (%s): %s\n", fontName, fname, gf_error_to_string(e) )); | |
| } else { | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MODULE, ("[FreeType] Font %s (%s) not found\n", fontName, fname)); | |
| e = GF_NOT_SUPPORTED; | |
| } | |
| gf_free(fname); | |
| gf_opts_set_key("temp_freetype", OrigFontName, "not found"); | |
| return e; | |
| } | |
| static GF_Err ft_get_font_info(GF_FontReader *dr, char **font_name, u32 *em_size, s32 *ascent, s32 *descent, s32 *underline, s32 *line_spacing, s32 *max_advance_h, s32 *max_advance_v) | |
| { | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| if (!ftpriv->active_face) return GF_BAD_PARAM; | |
| *em_size = ftpriv->active_face->units_per_EM; | |
| *ascent = ftpriv->active_face->ascender; | |
| *descent = ftpriv->active_face->descender; | |
| *underline = ftpriv->active_face->underline_position; | |
| *line_spacing = ftpriv->active_face->height; | |
| *font_name = gf_strdup(ftpriv->active_face->family_name); | |
| *max_advance_h = ftpriv->active_face->max_advance_width; | |
| *max_advance_v = ftpriv->active_face->max_advance_height; | |
| return GF_OK; | |
| } | |
| static GF_Err ft_get_glyphs(GF_FontReader *dr, const char *utf_string, u32 *glyph_buffer, u32 *io_glyph_buffer_size, const char *xml_lang, Bool *is_rtl) | |
| { | |
| u32 len; | |
| u32 i; | |
| u16 *conv; | |
| char *utf8 = (char*) utf_string; | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| if (!ftpriv->active_face) return GF_BAD_PARAM; | |
| /*TODO: glyph substitution / ligature */ | |
| len = utf_string ? (u32) strlen(utf_string) : 0; | |
| if (!len) { | |
| *io_glyph_buffer_size = 0; | |
| return GF_OK; | |
| } | |
| if (*io_glyph_buffer_size < len+1) { | |
| *io_glyph_buffer_size = len+1; | |
| return GF_BUFFER_TOO_SMALL; | |
| } | |
| len = gf_utf8_mbstowcs((u16*) glyph_buffer, *io_glyph_buffer_size, (const char **) &utf8); | |
| if (len == GF_UTF8_FAIL) return GF_IO_ERR; | |
| if (utf8) return GF_IO_ERR; | |
| /*perform bidi relayout*/ | |
| conv = (u16*) glyph_buffer; | |
| *is_rtl = gf_utf8_reorder_bidi(conv, len); | |
| /*move 16bit buffer to 32bit*/ | |
| for (i=len; i>0; i--) { | |
| glyph_buffer[i-1] = (u32) conv[i-1]; | |
| } | |
| *io_glyph_buffer_size = len; | |
| return GF_OK; | |
| } | |
| typedef struct | |
| { | |
| FTBuilder *ftpriv; | |
| GF_Path *path; | |
| s32 last_x, last_y; | |
| } ft_outliner; | |
| #if defined(GPAC_CONFIG_ANDROID) | |
| #define FTCST | |
| #else | |
| #define FTCST const | |
| #endif | |
| static int ft_move_to(FTCST FT_Vector *to, void *user) | |
| { | |
| ft_outliner *ftol = (ft_outliner *)user; | |
| gf_path_add_move_to(ftol->path, INT2FIX(to->x), INT2FIX(to->y) ); | |
| ftol->last_x = (s32) to->x; | |
| ftol->last_y = (s32) to->y; | |
| return 0; | |
| } | |
| static int ft_line_to(FTCST FT_Vector *to, void *user) | |
| { | |
| ft_outliner *ftol = (ft_outliner *)user; | |
| if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) { | |
| gf_path_close(ftol->path); | |
| } else { | |
| gf_path_add_line_to(ftol->path, INT2FIX(to->x), INT2FIX(to->y) ); | |
| } | |
| return 0; | |
| } | |
| static int ft_conic_to(FTCST FT_Vector * control, FTCST FT_Vector *to, void *user) | |
| { | |
| ft_outliner *ftol = (ft_outliner *)user; | |
| gf_path_add_quadratic_to(ftol->path, INT2FIX(control->x), INT2FIX(control->y), INT2FIX(to->x), INT2FIX(to->y) ); | |
| if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) gf_path_close(ftol->path); | |
| return 0; | |
| } | |
| static int ft_cubic_to(FTCST FT_Vector *c1, FTCST FT_Vector *c2, FTCST FT_Vector *to, void *user) | |
| { | |
| ft_outliner *ftol = (ft_outliner *)user; | |
| gf_path_add_cubic_to(ftol->path, INT2FIX(c1->x), INT2FIX(c1->y), INT2FIX(c2->x), INT2FIX(c2->y), INT2FIX(to->x), INT2FIX(to->y) ); | |
| if ( (ftol->last_x == to->x) && (ftol->last_y == to->y)) gf_path_close(ftol->path); | |
| return 0; | |
| } | |
| static GF_Glyph *ft_load_glyph(GF_FontReader *dr, u32 glyph_name) | |
| { | |
| GF_Glyph *glyph; | |
| u32 glyph_idx; | |
| FT_BBox bbox; | |
| FT_OutlineGlyph outline; | |
| ft_outliner outl; | |
| FT_Outline_Funcs ft_outl_funcs; | |
| FTBuilder *ftpriv = (FTBuilder *)dr->udta; | |
| if (!ftpriv->active_face || !glyph_name) return NULL; | |
| FT_Select_Charmap(ftpriv->active_face, FT_ENCODING_UNICODE); | |
| glyph_idx = FT_Get_Char_Index(ftpriv->active_face, glyph_name); | |
| /*missing glyph*/ | |
| if (!glyph_idx) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_MODULE, ("[FreeType] Glyph not found for char %d in font %s (style %s)\n", glyph_name, ftpriv->active_face->family_name, ftpriv->active_face->style_name)); | |
| return NULL; | |
| } | |
| /*work in design units*/ | |
| FT_Load_Glyph(ftpriv->active_face, glyph_idx, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP); | |
| FT_Get_Glyph(ftpriv->active_face->glyph, (FT_Glyph*)&outline); | |
| /*oops not vectorial...*/ | |
| if (outline->root.format==FT_GLYPH_FORMAT_BITMAP) return NULL; | |
| GF_SAFEALLOC(glyph, GF_Glyph); | |
| if (!glyph) return NULL; | |
| GF_SAFEALLOC(glyph->path, GF_Path); | |
| if (!glyph->path) { | |
| gf_free(glyph); | |
| return NULL; | |
| } | |
| /*setup outliner*/ | |
| ft_outl_funcs.shift = 0; | |
| ft_outl_funcs.delta = 0; | |
| ft_outl_funcs.move_to = ft_move_to; | |
| ft_outl_funcs.line_to = ft_line_to; | |
| ft_outl_funcs.conic_to = ft_conic_to; | |
| ft_outl_funcs.cubic_to = ft_cubic_to; | |
| outl.path = glyph->path; | |
| outl.ftpriv = ftpriv; | |
| /*FreeType is marvelous and gives back the right advance on space char !!!*/ | |
| FT_Outline_Decompose(&outline->outline, &ft_outl_funcs, &outl); | |
| FT_Glyph_Get_CBox((FT_Glyph) outline, ft_glyph_bbox_unscaled, &bbox); | |
| glyph->ID = glyph_name; | |
| glyph->utf_name = glyph_name; | |
| glyph->horiz_advance = (s32) ftpriv->active_face->glyph->metrics.horiAdvance; | |
| glyph->vert_advance = (s32) ftpriv->active_face->glyph->metrics.vertAdvance; | |
| /* | |
| glyph->x = bbox.xMin; | |
| glyph->y = bbox.yMax; | |
| */ | |
| glyph->width = (u32) ftpriv->active_face->glyph->metrics.width; | |
| glyph->height = (u32) ftpriv->active_face->glyph->metrics.height; | |
| FT_Done_Glyph((FT_Glyph) outline); | |
| return glyph; | |
| } | |
| static GF_FontReader *ft_load() | |
| { | |
| GF_FontReader *dr; | |
| FTBuilder *ftpriv; | |
| dr = gf_malloc(sizeof(GF_FontReader)); | |
| memset(dr, 0, sizeof(GF_FontReader)); | |
| GF_REGISTER_MODULE_INTERFACE(dr, GF_FONT_READER_INTERFACE, "freetype", "gpac distribution"); | |
| ftpriv = gf_malloc(sizeof(FTBuilder)); | |
| memset(ftpriv, 0, sizeof(FTBuilder)); | |
| ftpriv->font_dirs = gf_list_new(); | |
| ftpriv->loaded_fonts = gf_list_new(); | |
| dr->udta = ftpriv; | |
| dr->init_font_engine = ft_init_font_engine; | |
| dr->shutdown_font_engine = ft_shutdown_font_engine; | |
| dr->set_font = ft_set_font; | |
| dr->get_font_info = ft_get_font_info; | |
| dr->get_glyphs = ft_get_glyphs; | |
| dr->load_glyph = ft_load_glyph; | |
| return dr; | |
| } | |
| static void ft_delete(GF_BaseInterface *ifce) | |
| { | |
| GF_FontReader *dr = (GF_FontReader *) ifce; | |
| FTBuilder *ftpriv = dr->udta; | |
| while (gf_list_count(ftpriv->font_dirs)) { | |
| char *font = gf_list_pop_back(ftpriv->font_dirs); | |
| if (font) | |
| gf_free(font); | |
| } | |
| gf_list_del(ftpriv->font_dirs); | |
| if (ftpriv->font_serif) gf_free(ftpriv->font_serif); | |
| if (ftpriv->font_sans) gf_free(ftpriv->font_sans); | |
| if (ftpriv->font_fixed) gf_free(ftpriv->font_fixed); | |
| if (ftpriv->font_default) gf_free(ftpriv->font_default); | |
| gf_list_del(ftpriv->loaded_fonts); | |
| gf_free(dr->udta); | |
| gf_free(dr); | |
| } | |
| #ifndef GPAC_STANDALONE_RENDER_2D | |
| GPAC_MODULE_EXPORT | |
| const u32 *QueryInterfaces() | |
| { | |
| static u32 si [] = { | |
| GF_FONT_READER_INTERFACE, | |
| 0 | |
| }; | |
| return si; | |
| } | |
| GPAC_MODULE_EXPORT | |
| GF_BaseInterface *LoadInterface(u32 InterfaceType) | |
| { | |
| if (InterfaceType == GF_FONT_READER_INTERFACE) return (GF_BaseInterface *)ft_load(); | |
| return NULL; | |
| } | |
| GPAC_MODULE_EXPORT | |
| void ShutdownInterface(GF_BaseInterface *ifce) | |
| { | |
| switch (ifce->InterfaceType) { | |
| case GF_FONT_READER_INTERFACE: | |
| ft_delete(ifce); | |
| break; | |
| } | |
| } | |
| GPAC_MODULE_STATIC_DECLARATION( ftfont ) | |
| #endif | |
| #endif //GPAC_DISABLE_EVG |