Пример #1
0
/// @copydoc ResourceHandler::CacheResource()
bool FontResourceHandler::CacheResource(
    ObjectPreprocessor* pObjectPreprocessor,
    Resource* pResource,
    const String& rSourceFilePath )
{
    HELIUM_ASSERT( pObjectPreprocessor );
    HELIUM_ASSERT( pResource );

    Font* pFont = Reflect::AssertCast< Font >( pResource );

    // Load the font into memory ourselves in order to make sure we properly support Unicode file names.
    FileStream* pFileStream = File::Open( rSourceFilePath, FileStream::MODE_READ );
    if( !pFileStream )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Source file for font resource \"%s\" failed to open properly.\n" ),
            *rSourceFilePath );

        return false;
    }

    uint64_t fileSize64 = static_cast< uint64_t >( pFileStream->GetSize() );
    if( fileSize64 > SIZE_MAX )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Font file \"%s\" exceeds the maximum addressable size of data in memory for " )
              TXT( "this platform and will not be cached.\n" ) ),
            *rSourceFilePath );

        delete pFileStream;

        return false;
    }

    size_t fileSize = static_cast< size_t >( fileSize64 );

    uint8_t* pFileData = new uint8_t [ fileSize ];
    if( !pFileData )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Failed to allocate %" ) TPRIuSZ TXT( " bytes for resource data for font " )
              TXT( "\"%s\".\n" ) ),
            fileSize,
            *rSourceFilePath );

        delete pFileStream;

        return false;
    }

    size_t bytesRead = pFileStream->Read( pFileData, 1, fileSize );
    delete pFileStream;

    if( bytesRead != fileSize )
    {
        HELIUM_TRACE(
            TRACE_WARNING,
            ( TXT( "FontResourceHandler: Attempted to read %" ) TPRIuSZ TXT( " bytes from font resource file \"%s\", " )
              TXT( "but only %" ) TPRIuSZ TXT( " bytes were read successfully.\n" ) ),
            fileSize,
            *rSourceFilePath,
            bytesRead );
    }

    // Create the font face.
    FT_Library pLibrary = GetStaticLibrary();
    HELIUM_ASSERT( pLibrary );

    FT_Face pFace = NULL;
    FT_Error error = FT_New_Memory_Face( pLibrary, pFileData, static_cast< FT_Long >( bytesRead ), 0, &pFace );
    if( error != 0 )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Failed to create font face from resource file \"%s\".\n" ),
            *rSourceFilePath );

        delete [] pFileData;

        return false;
    }

    // Set the appropriate font size.
    int32_t pointSize = Font::Float32ToFixed26x6( pFont->GetPointSize() );
    uint32_t dpi = pFont->GetDpi();

    error = FT_Set_Char_Size( pFace, pointSize, pointSize, dpi, dpi );
    if( error != 0 )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            TXT( "FontResourceHandler: Failed to set size of font resource \"%s\".\n" ),
            *rSourceFilePath );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    // Get the general font size information.
    FT_Size pSize = pFace->size;
    HELIUM_ASSERT( pSize );

    int32_t ascender = pSize->metrics.ascender;
    int32_t descender = pSize->metrics.descender;
    int32_t height = pSize->metrics.height;
    int32_t maxAdvance = pSize->metrics.max_advance;

    // Make sure that all characters in the font will fit on a single texture sheet (note that we also need at least a
    // pixel on each side in order to pad each glyph).
    uint16_t textureSheetWidth = Max< uint16_t >( pFont->GetTextureSheetWidth(), 1 );
    uint16_t textureSheetHeight = Max< uint16_t >( pFont->GetTextureSheetHeight(), 1 );

    int32_t integerHeight = ( height + ( 1 << 6 ) - 1 ) >> 6;
    if( integerHeight + 2 > textureSheetHeight )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Font height (%" ) TPRId32 TXT( ") exceeds the texture sheet height (%" )
              TPRIu16 TXT( ") for font resource \"%s\".\n" ) ),
            integerHeight,
            textureSheetHeight,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    int32_t integerMaxAdvance = ( maxAdvance + ( 1 << 6 ) - 1 ) >> 6;
    if( integerMaxAdvance + 2 > textureSheetWidth )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Maximum character advance (%" ) TPRId32 TXT( ") exceeds the texture sheet " )
              TXT( "width (%" ) TPRIu16 TXT( ") for font resource \"%s\".\n" ) ),
            integerMaxAdvance,
            textureSheetWidth,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    // Allocate a buffer for building our texture sheets.
    uint_fast32_t texturePixelCount =
        static_cast< uint_fast32_t >( textureSheetWidth ) * static_cast< uint_fast32_t >( textureSheetHeight );
    uint8_t* pTextureBuffer = new uint8_t [ texturePixelCount ];
    HELIUM_ASSERT( pTextureBuffer );
    if( !pTextureBuffer )
    {
        HELIUM_TRACE(
            TRACE_ERROR,
            ( TXT( "FontResourceHandler: Failed to allocate %" ) TPRIuFAST32 TXT( " bytes for texture resource " )
              TXT( "buffer data while caching font resource \"%s\".\n" ) ),
            texturePixelCount,
            *pResource->GetPath().ToString() );

        FT_Done_Face( pFace );
        delete [] pFileData;

        return false;
    }

    MemoryZero( pTextureBuffer, texturePixelCount );

    // Build the texture sheets for our glyphs.
    Font::ECompression textureCompression = pFont->GetTextureCompression();
    bool bAntialiased = pFont->GetAntialiased();

    DynArray< DynArray< uint8_t > > textureSheets;
    DynArray< Font::Character > characters;

    uint16_t penX = 1;
    uint16_t penY = 1;
    uint16_t lineHeight = 0;

    FT_Int32 glyphLoadFlags = FT_LOAD_RENDER;
    if( !bAntialiased )
    {
        glyphLoadFlags |= FT_LOAD_TARGET_MONO;
    }

    for( uint_fast32_t codePoint = 0; codePoint <= UNICODE_CODE_POINT_MAX; ++codePoint )
    {
        // Check whether the current code point is contained within the font.
        FT_UInt characterIndex = FT_Get_Char_Index( pFace, static_cast< FT_ULong >( codePoint ) );
        if( characterIndex == 0 )
        {
            continue;
        }

        // Load and render the glyph for the current character.
        HELIUM_VERIFY( FT_Load_Glyph( pFace, characterIndex, glyphLoadFlags ) == 0 );

        FT_GlyphSlot pGlyph = pFace->glyph;
        HELIUM_ASSERT( pGlyph );

        // Proceed to the next line in the texture sheet or the next sheet itself if we don't have enough room in the
        // current line/sheet.
        HELIUM_ASSERT( pGlyph->bitmap.rows >= 0 );
        HELIUM_ASSERT( pGlyph->bitmap.width >= 0 );
        uint_fast32_t glyphRowCount = static_cast< uint32_t >( pGlyph->bitmap.rows );
        uint_fast32_t glyphWidth = static_cast< uint32_t >( pGlyph->bitmap.width );

        if( penX + glyphWidth + 1 >= textureSheetWidth )
        {
            penX = 1;

            if( penY + glyphRowCount + 1 >= textureSheetHeight )
            {
                CompressTexture(
                    pTextureBuffer,
                    textureSheetWidth,
                    textureSheetHeight,
                    textureCompression,
                    textureSheets );
                MemoryZero( pTextureBuffer, texturePixelCount );

                penY = 1;
            }
            else
            {
                penY += lineHeight + 1;
            }

            lineHeight = 0;
        }

        // Copy the character data from the glyph bitmap to the texture sheet.
        int_fast32_t glyphPitch = pGlyph->bitmap.pitch;

        const uint8_t* pGlyphBuffer = pGlyph->bitmap.buffer;
        HELIUM_ASSERT( pGlyphBuffer || glyphRowCount == 0 );

        uint8_t* pTexturePixel =
            pTextureBuffer + static_cast< size_t >( penY ) * static_cast< size_t >( textureSheetWidth ) + penX;

        if( bAntialiased )
        {
            // Anti-aliased fonts are rendered as 8-bit grayscale images, so just copy the data as-is.
            for( uint_fast32_t rowIndex = 0; rowIndex < glyphRowCount; ++rowIndex )
            {
                MemoryCopy( pTexturePixel, pGlyphBuffer, glyphWidth );
                pGlyphBuffer += glyphPitch;
                pTexturePixel += textureSheetWidth;
            }
        }
        else
        {
            // Fonts without anti-aliasing are rendered as 1-bit monochrome images, so we need to manually convert each
            // row to 8-bit grayscale.
            for( uint_fast32_t rowIndex = 0; rowIndex < glyphRowCount; ++rowIndex )
            {
                const uint8_t* pGlyphPixelBlock = pGlyphBuffer;
                pGlyphBuffer += glyphPitch;

                uint8_t* pCurrentTexturePixel = pTexturePixel;
                pTexturePixel += textureSheetWidth;

                uint_fast32_t remainingPixelCount = glyphWidth;
                while( remainingPixelCount >= 8 )
                {
                    remainingPixelCount -= 8;

                    uint8_t pixelBlock = *pGlyphPixelBlock;
                    ++pGlyphPixelBlock;

                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 7 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 6 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 5 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 4 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 3 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 2 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 1 ) ) ? 255 : 0 );
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & ( 1 << 0 ) ) ? 255 : 0 );
                }

                uint8_t pixelBlock = *pGlyphPixelBlock;
                uint8_t mask = ( 1 << 7 );
                while( remainingPixelCount != 0 )
                {
                    *( pCurrentTexturePixel++ ) = ( ( pixelBlock & mask ) ? 255 : 0 );
                    mask >>= 1;
                    --remainingPixelCount;
                }
            }
        }

        // Store the character information in our character array.
        Font::Character* pCharacter = characters.New();
        HELIUM_ASSERT( pCharacter );
        pCharacter->codePoint = static_cast< uint32_t >( codePoint );

        pCharacter->imageX = penX;
        pCharacter->imageY = penY;
        pCharacter->imageWidth = static_cast< uint16_t >( glyphWidth );
        pCharacter->imageHeight = static_cast< uint16_t >( glyphRowCount );

        pCharacter->width = pGlyph->metrics.width;
        pCharacter->height = pGlyph->metrics.height;
        pCharacter->bearingX = pGlyph->metrics.horiBearingX;
        pCharacter->bearingY = pGlyph->metrics.horiBearingY;
        pCharacter->advance = pGlyph->metrics.horiAdvance;

        HELIUM_ASSERT( textureSheets.GetSize() < UINT8_MAX );
        pCharacter->texture = static_cast< uint8_t >( static_cast< uint8_t >( textureSheets.GetSize() ) );

        // Update the pen location as well as the maximum line height as appropriate based on the current line height.
        penX += static_cast< uint16_t >( glyphWidth ) + 1;

        HELIUM_ASSERT( glyphRowCount <= UINT16_MAX );
        lineHeight = Max< uint16_t >( lineHeight, static_cast< uint16_t >( glyphRowCount ) );
    }

    // Compress and store the last texture in the sheet.
    if( !characters.IsEmpty() )
    {
        CompressTexture( pTextureBuffer, textureSheetWidth, textureSheetHeight, textureCompression, textureSheets );
    }

    // Done processing the font itself, so free some resources.
    delete [] pTextureBuffer;

    FT_Done_Face( pFace );
    delete [] pFileData;

    // Cache the font data.
    size_t characterCountActual = characters.GetSize();
    HELIUM_ASSERT( characterCountActual <= UINT32_MAX );
    uint32_t characterCount = static_cast< uint32_t >( characterCountActual );

    size_t textureCountActual = textureSheets.GetSize();
    HELIUM_ASSERT( textureCountActual < UINT8_MAX );
    uint8_t textureCount = static_cast< uint8_t >( textureCountActual );

    BinarySerializer persistentDataSerializer;
    for( size_t platformIndex = 0; platformIndex < static_cast< size_t >( Cache::PLATFORM_MAX ); ++platformIndex )
    {
        PlatformPreprocessor* pPreprocessor = pObjectPreprocessor->GetPlatformPreprocessor(
            static_cast< Cache::EPlatform >( platformIndex ) );
        if( !pPreprocessor )
        {
            continue;
        }

        persistentDataSerializer.SetByteSwapping( pPreprocessor->SwapBytes() );
        persistentDataSerializer.BeginSerialize();

        persistentDataSerializer << ascender;
        persistentDataSerializer << descender;
        persistentDataSerializer << height;
        persistentDataSerializer << maxAdvance;
        persistentDataSerializer << characterCount;
        persistentDataSerializer << textureCount;

        for( size_t characterIndex = 0; characterIndex < characterCountActual; ++characterIndex )
        {
            characters[ characterIndex ].Serialize( persistentDataSerializer );
        }

        persistentDataSerializer.EndSerialize();

        Resource::PreprocessedData& rPreprocessedData = pResource->GetPreprocessedData(
            static_cast< Cache::EPlatform >( platformIndex ) );
        rPreprocessedData.persistentDataBuffer = persistentDataSerializer.GetPropertyStreamBuffer();
        rPreprocessedData.subDataBuffers = textureSheets;
        rPreprocessedData.bLoaded = true;
    }

    return true;
}