CEGUIFont资源加载流程

Posted 林多

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CEGUIFont资源加载流程相关的知识,希望对你有一定的参考价值。

CEGUI Font资源加载流程

  • Font(字体),主要两个类型的字体:位图字体、矢量字体。
  • 位图字体:PixmapFont,相当于每个字形(glyph)对应一个图片元素。有时候也称为光栅字体。
  • 矢量字体:FreetypeFont,动态字体。每个字形(glyph)由数学矢量组成,支持任意缩放。
  • 本文以CEGUI 0.8.7为例子

Font加载流程

  • CEGUI种可以通过Scheme文件加载,也可以使用下面的代码显式加载。
FontManager::getSingleton().createFromFile("DejaVuSans-12.font");
  • DejaVuSans-12.font 文件内容。
<?xml version="1.0" ?>
<Font version="3" name="DejaVuSans-12" filename="DejaVuSans.ttf" type="FreeType" size="12" nativeHorzRes="1280" nativeVertRes="720" autoScaled="vertical"/>

  • createFromFile会调用到其父类NamedXMLResourceManager的定义。
//----------------------------------------------------------------------------//
template<typename T, typename U>
T& NamedXMLResourceManager<T, U>::createFromFile(const String& xml_filename,
                                                 const String& resource_group,
                                                 XMLResourceExistsAction action)

    U xml_loader;

    xml_loader.handleFile(xml_filename, resource_group);
    return doExistingObjectAction(xml_loader.getObjectName(),
                                  &xml_loader.getObject(), action);


  • 这里 U为 Font_xmlHandler 类型,T为 Font类型。Font_xmlHandler为重载handleFile,调用到其父类XMLHandler中的定义。
    void XMLHandler::handleFile(const String& fileName, const String& resourceGroup)
    
        System::getSingleton().getXMLParser()->parseXMLFile(
                    *this, fileName, getSchemaName(),
                    resourceGroup.empty() ? getDefaultResourceGroup() :
                                             resourceGroup);
    
  • 调用到XMLParser中的parseXMLFile函数。FileName就是字体资源文件名,shcemaName为“Font.xsd”,resourceGroup为CEGUI中资源组的概念(可以理解为资源路径),接下来开始解析 .Font文件。这里注意一点,对于Font加载,传入的this,为Font_xmlHandler这个类型。
    void XMLParser::parseXMLFile(XMLHandler& handler, const String& filename, const String& schemaName, const String& resourceGroup)
    
        // 省略
        // Acquire resource using CEGUI ResourceProvider
        // 这里会把.font文件内容,读入内存
        RawDataContainer rawXMLData;
        System::getSingleton().getResourceProvider()->loadRawDataContainer(filename, rawXMLData, resourceGroup);

        CEGUI_TRY
        
            // The actual parsing action (this is overridden and depends on the specific parser)
            // 这里开始解析.Font文件中的元素
            parseXML(handler, rawXMLData, schemaName);
        
        CEGUI_CATCH (const Exception&)
        
			// 省略,这里是一些异常的处理
        

        // Release resource
        // 释放资源
        System::getSingleton().getResourceProvider()->unloadRawDataContainer(rawXMLData);
    
  • 接下来调用具体xml解析模块的parseXML函数,这里handler(Font_xmlHandler)传入。例如走的是LibxmlParser
void LibxmlParser::parseXML(XMLHandler& handler,
                            const RawDataContainer& source,
                            const String& /*schemaName*/)

	// 从内存中解析,因为之前已经将.font文件内容,加载到内存中了。
    xmlDocPtr doc = xmlParseMemory(
        reinterpret_cast<const char*>(source.getDataPtr()),
        source.getSize());

    if (!doc)
    
        xmlError* err = xmlGetLastError();

        CEGUI_THROW(GenericException(
            String("xmlParseMemory failed in file: '") +
            err->file + "' at line number" +
            PropertyHelper<uint>::toString(err->line) + ".  Error is:" +
            err->message));
    

    // get root element
    xmlNode* root = xmlDocGetRootElement(doc);

    // 这里这里开始解析xml元素。
    processXMLElement(handler, root);

    // release the xmlDoc 
    xmlFreeDoc(doc);

  • processXMLElement的入参为Handler(Font_xmlHandler)和XML的RootElement。
// internal helper function to process elements
void processXMLElement(XMLHandler& handler, xmlNode* node)

    // build attributes block for the element
    // 省略
    // element start processing
    // Font_xmlHandler在这里发挥了这种。element的信息,在这里传给对应的handler。
    handler.elementStart(reinterpret_cast<const encoded_char*>(node->name), attrs);
	// 遍历
    for (xmlNode* cur_node = node->children; cur_node; cur_node = cur_node->next)
    
        switch(cur_node->type)
        
        case XML_ELEMENT_NODE:
            // 迭代
            processXMLElement(handler, cur_node);
            break;

        case XML_TEXT_NODE:
            if (cur_node->content != 0 && *cur_node->content!= '\\0')
                handler.text(reinterpret_cast<const encoded_char*>(cur_node->content));
            break;

        default:
            break;
        
    

    // element end processing
    // 结束解析
    handler.elementEnd(reinterpret_cast<const encoded_char*>(node->name));

  • 这里最重要的是,调用了Font_xmlHandler的elementStart,这里面会真正加载.ttf字体。
//----------------------------------------------------------------------------//
void Font_xmlHandler::elementStart(const String& element,
                                   const XMLAttributes& attributes)

    // handle root Font element
    if (element == FontElement) // 解析 <Font />中的内容。
        elementFontStart(attributes);   
    // handle a Mapping element
    else if (element == MappingElement)
        elementMappingStart(attributes);
    // anything else is a non-fatal error.
    else
        Logger::getSingleton().logEvent("Font_xmlHandler::elementStart: "
            "Unknown element encountered: <" + element + ">", Errors);

  • 这个函数里,根据Element Name,做不同的处理。elementFontStart函数中,会根据 Font元素中的内容,加载字体文件。
void Font_xmlHandler::elementFontStart(const XMLAttributes& attributes)

    validateFontFileVersion(attributes);

    // get type of font being created
    // 获取字体类型 
    // 最上面的.font文件中,写明了 type="FreeType"
    const String font_type(attributes.getValueAsString(FontTypeAttribute));

    // log the start of font creation.
    CEGUI_LOGINSANE(
        "Started creation of Font from XML specification:");

    if (font_type == FontTypeFreeType)  // 矢量字体
        createFreeTypeFont(attributes);
    else if (font_type == FontTypePixmap)  // 位图字体
        createPixmapFont(attributes);
    else
        CEGUI_THROW(InvalidRequestException(
            "Encountered unknown font type of '" + font_type + "'"));


  • 接下来调用 createFreeTypeFont,加载矢量字体。
void Font_xmlHandler::createFreeTypeFont(const XMLAttributes& attributes)

	// name="DejaVuSans-12"
    const String name(attributes.getValueAsString(FontNameAttribute));
    // filename="DejaVuSans.ttf"
    const String filename(attributes.getValueAsString(FontFilenameAttribute));
    const String resource_group(attributes.getValueAsString(FontResourceGroupAttribute));

#ifdef CEGUI_HAS_FREETYPE
	// 创建FreeTypeFont对象,FreeTypeFont在创建过程中,会加载ttf字体文件
	// 并,构建 字形索引 和字符映射表
    d_font = CEGUI_NEW_AO FreeTypeFont(name,
        attributes.getValueAsFloat(FontSizeAttribute, 12.0f),
        attributes.getValueAsBool(FontAntiAliasedAttribute, true),
        filename, resource_group,
        PropertyHelper<AutoScaledMode>::fromString(
                attributes.getValueAsString(FontAutoScaledAttribute)),
        Sizef(attributes.getValueAsFloat(FontNativeHorzResAttribute, 640.0f),
              attributes.getValueAsFloat(FontNativeVertResAttribute, 480.0f)),
        attributes.getValueAsFloat(FontLineSpacingAttribute, 0.0f));
#else
    CEGUI_THROW(InvalidRequestException(
        "CEGUI was compiled without freetype support."));
#endif

  • 创建FreeTypeFont对象
FreeTypeFont::FreeTypeFont(const String& font_name, const float point_size,
                           const bool anti_aliased, const String& font_filename,
                           const String& resource_group,
                           const AutoScaledMode auto_scaled,
                           const Sizef& native_res,
                           const float specific_line_spacing) :
    Font(font_name, Font_xmlHandler::FontTypeFreeType, font_filename,
         resource_group, auto_scaled, native_res),
    d_specificLineSpacing(specific_line_spacing),
    d_ptSize(point_size),
    d_antiAliased(anti_aliased),
    d_fontFace(0)

    if (!ft_usage_count++)
        FT_Init_FreeType(&ft_lib);  // 初始化 FreeType
    addFreeTypeFontProperties();
    updateFont(); // 这里会使用 freetype字体引擎处理字体文件
    char tmp[50];
    snprintf(tmp, sizeof(tmp), "Successfully loaded %d glyphs",
             static_cast<int>(d_cp_map.size()));
    Logger::getSingleton().logEvent(tmp, Informative);

  • freetype是一个开源的字体引擎库,关于其概念和使用可以字形百度。接下来updateFont,会使用freetype加载ttf字体文件,进行相关的初始化。
void FreeTypeFont::updateFont()

    free();
	// 将.ttf 读入到内存中
    System::getSingleton().getResourceProvider()->loadRawDataContainer(
        d_filename, d_fontData, d_resourceGroup.empty() ?
            getDefaultResourceGroup() : d_resourceGroup);

    FT_Error error;

	// 使用FreeType创建FontFace对象
    // create face using input font
    if ((error = FT_New_Memory_Face(ft_lib, d_fontData.getDataPtr(),
                           static_cast<FT_Long>(d_fontData.getSize()), 0,
                           &d_fontFace)) != 0)
        CEGUI_THROW(GenericException("Failed to create face from font file '" +
            d_filename + "' error was: " +
            ((error < FT_Err_Max) ? ft_errors[error] : "unknown error")));

    // check that default Unicode character map is available
    if (!d_fontFace->charmap)
    
        FT_Done_Face(d_fontFace);
        d_fontFace = 0;
        CEGUI_THROW(GenericException(
            "The font '" + d_name + "' does not have a Unicode charmap, and "
            "cannot be used."));
    

    uint horzdpi = static_cast<uint>(System::getSingleton().getRenderer()->getDisplayDPI().d_x);
    uint vertdpi = static_cast<uint>(System::getSingleton().getRenderer()->getDisplayDPI().d_y);

    float hps = d_ptSize * 64;
    float vps = d_ptSize * 64;
    if (d_autoScaled != ASM_Disabled)
    
        hps *= d_horzScaling;
        vps *= d_vertScaling;
    
    // 设置像素尺寸,其实就是根据字号,进行设定
    if (FT_Set_Char_Size(d_fontFace, FT_F26Dot6(hps), FT_F26Dot6(vps), horzdpi, vertdpi))
    
        // For bitmap fonts we can render only at specific point sizes.
        // Try to find nearest point size and use it, if that is possible
        float ptSize_72 = (d_ptSize * 72.0f) / vertdpi;
        float best_delta = 99999;
        float best_size = 0;
        for (int i = 0; i < d_fontFace->num_fixed_sizes; i++)
        
            float size = d_fontFace->available_sizes [i].size * float(FT_POS_COEF);
            float delta = fabs(size - ptSize_72);
            if (delta < best_delta)
            
                best_delta = delta;
                best_size = size;
            
        

        if ((best_size <= 0) ||
                FT_Set_Char_Size(d_fontFace, 0, FT_F26Dot6(best_size * 64), 0, 0))
        
            char size [20];
            snprintf(size, sizeof(size), "%g", d_ptSize);
            CEGUI_THROW(GenericException("The font '" + d_name + "' cannot be "
                "rasterised at a size of " + size + " points, and cannot be "
                "used."));
        
    

    if (d_fontFace->face_flags & FT_FACE_FLAG_SCALABLE)
    
        //float x_scale = d_fontFace->size->metrics.x_scale * FT_POS_COEF * (1.0/65536.0);
        float y_scale = d_fontFace->size->metrics.y_scale * float(FT_POS_COEF) * (1.0f / 65536.0f);
        d_ascender = d_fontFace->ascender * y_scale;
        d_descender = d_fontFace->descender * y_scale;
        d_height = d_fontFace->height * y_scale;
    
    else
    
        d_ascender = d_fontFace->size->metrics.ascender * float(FT_POS_COEF);
        d_descender = d_fontFace->size->metrics.descender * float(FT_POS_COEF);
        d_height = d_fontFace->size->metrics.height * float(FT_POS_COEF);
    

    if (d_specificLineSpacing > 0.0f)
    
        d_height = d_specificLineSpacing;
    
    // 构建字符映像
    initialiseGlyphMap();

  • 字符映像其实是 字符码与字形索引的映射关系。字符码,就是某个字符在某种编码下得值,比如C的ASCII码为83。字形索引,就是字体文件中用来查找某个字形的索引。通过字体文件提供的字符映射表,来查找某个字符码对应的字形索引。
void FreeTypeFont::initialiseGlyphMap()

    FT_UInt gindex;
    FT_ULong codepoint = FT_Get_First_Char(d_fontFace, &gindex);
    FT_ULong max_codepoint = codepoint;

    while (gindex)
    
        if (max_codepoint < codepoint)
            max_codepoint = codepoint;
		// 这里将字体中,所有的字符码,存储到map中。
        d_cp_map[codepoint] = FontGlyph();

        codepoint = FT_Get_Next_Char(d_fontFace, codepoint, &gindex);
    

    setMaxCodepoint(max_codepoint);

  • 到这里完成了字体加载的主要部分。字体渲染时,会根据传入的字符码,使用FT_Load_Char函数,将对应的字符码渲染出位图,并进行OpenGL的相应纹理(CEGUI的Render为OPENGL类型时)进行渲染。

以上是关于CEGUIFont资源加载流程的主要内容,如果未能解决你的问题,请参考以下文章

CEGUIFont资源加载流程

CEGUI字体加载优化

CEGUI字体加载优化

CEGUI字体加载优化

使用光栅显示 tiff 图像的特定部分,而无需加载整个文件

CEGUI资源加载流程