X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Frendering%2Fatlas%2Ftext-atlas-renderer.cpp;h=a2faebcdc9497ac342f78882795b101b2d7cea6f;hb=51bd29a1b76ba44dd3b0cc34db1193ab83fa7661;hp=0ed74bdd41723f8a32e646281f71e0dece64da4c;hpb=3b1fb566901d21b8303d9be3308e3920f5182e6d;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp b/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp index 0ed74bd..c3663dd 100644 --- a/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp +++ b/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2016 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,19 @@ #include // EXTERNAL INCLUDES -#include +#include +#include +#include +#include +#include +#include // INTERNAL INCLUDES -#include +#include +#include #include -#include -#include +#include +#include using namespace Dali; using namespace Dali::Toolkit; @@ -33,133 +39,614 @@ using namespace Dali::Toolkit::Text; namespace { - const Vector2 DEFAULT_ATLAS_SIZE( 512.0f, 512.0f ); - const Vector2 DEFAULT_BLOCK_SIZE( 16.0f, 16.0f ); - const Vector2 PADDING( 2.0f, 2.0f ); +#if defined(DEBUG_ENABLED) + Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_RENDERING"); +#endif + +#define MAKE_SHADER(A)#A + +const char* VERTEX_SHADER = MAKE_SHADER( +attribute mediump vec2 aPosition; +attribute mediump vec2 aTexCoord; +attribute mediump vec4 aColor; +uniform mediump vec2 uOffset; +uniform mediump mat4 uMvpMatrix; +varying mediump vec2 vTexCoord; +varying mediump vec4 vColor; + +void main() +{ + mediump vec4 position = vec4( aPosition.xy + uOffset, 0.0, 1.0 ); + gl_Position = uMvpMatrix * position; + vTexCoord = aTexCoord; + vColor = aColor; +} +); + +const char* FRAGMENT_SHADER_L8 = MAKE_SHADER( +uniform lowp vec4 uColor; +uniform lowp vec4 textColorAnimatable; +uniform sampler2D sTexture; +varying mediump vec2 vTexCoord; +varying mediump vec4 vColor; + +void main() +{ + mediump vec4 color = texture2D( sTexture, vTexCoord ); + gl_FragColor = vec4( vColor.rgb * uColor.rgb * textColorAnimatable.rgb, vColor.a * textColorAnimatable.a * color.r ); +} +); + +const char* FRAGMENT_SHADER_RGBA = MAKE_SHADER( +uniform lowp vec4 uColor; +uniform lowp vec4 textColorAnimatable; +uniform sampler2D sTexture; +varying mediump vec2 vTexCoord; + +void main() +{ + gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor * textColorAnimatable; +} +); + +const float ZERO( 0.0f ); +const float HALF( 0.5f ); +const float ONE( 1.0f ); +const uint32_t DEFAULT_ATLAS_WIDTH = 512u; +const uint32_t DEFAULT_ATLAS_HEIGHT = 512u; } struct AtlasRenderer::Impl { + enum Style + { + STYLE_NORMAL, + STYLE_DROP_SHADOW + }; struct MeshRecord { + MeshRecord() + : mAtlasId( 0u ) + { + } + uint32_t mAtlasId; - MeshData mMeshData; + AtlasManager::Mesh2D mMesh; + FrameBufferImage mBuffer; }; - struct AtlasRecord + /** + * brief Struct used to generate the underline mesh. + * There is one Extent per line of text. + */ + struct Extent { - uint32_t mImageId; + Extent() + : mBaseLine( 0.0f ), + mLeft( 0.0f ), + mRight( 0.0f ), + mUnderlinePosition( 0.0f ), + mUnderlineThickness( 0.0f ), + mMeshRecordIndex( 0u ) + { + } + + float mBaseLine; + float mLeft; + float mRight; + float mUnderlinePosition; + float mUnderlineThickness; + uint32_t mMeshRecordIndex; + }; + + struct MaxBlockSize + { + MaxBlockSize() + : mFontId( 0 ), + mNeededBlockWidth( 0 ), + mNeededBlockHeight( 0 ) + { + } + + FontId mFontId; + uint32_t mNeededBlockWidth; + uint32_t mNeededBlockHeight; + }; + + struct CheckEntry + { + CheckEntry() + : mFontId( 0 ), + mIndex( 0 ) + { + } + + FontId mFontId; + Text::GlyphIndex mIndex; + }; + + struct TextCacheEntry + { + TextCacheEntry() + : mFontId( 0 ), + mIndex( 0 ), + mImageId( 0 ) + { + } + + FontId mFontId; Text::GlyphIndex mIndex; + uint32_t mImageId; }; Impl() - : mSlotDelegate( this ) + : mDepth( 0 ) { mGlyphManager = AtlasGlyphManager::Get(); mFontClient = TextAbstraction::FontClient::Get(); - mGlyphManager.SetAtlasSize( DEFAULT_ATLAS_SIZE, DEFAULT_BLOCK_SIZE ); - mBasicShader = BasicShader::New(); - mBGRAShader = BgraShader::New(); + + mQuadVertexFormat[ "aPosition" ] = Property::VECTOR2; + mQuadVertexFormat[ "aTexCoord" ] = Property::VECTOR2; + mQuadVertexFormat[ "aColor" ] = Property::VECTOR4; } - void AddGlyphs( const std::vector& positions, const Vector& glyphs ) + bool IsGlyphUnderlined( GlyphIndex index, + const Vector& underlineRuns ) + { + for( Vector::ConstIterator it = underlineRuns.Begin(), + endIt = underlineRuns.End(); + it != endIt; + ++it ) + { + const GlyphRun& run = *it; + + if( ( run.glyphIndex <= index ) && ( index < run.glyphIndex + run.numberOfGlyphs ) ) + { + return true; + } + } + + return false; + } + + void AddGlyphs( Text::ViewInterface& view, + Actor textControl, + Property::Index animatablePropertyIndex, + const Vector& positions, + const Vector& glyphs, + const Vector4& defaultColor, + const Vector4* const colorsBuffer, + const ColorIndex* const colorIndicesBuffer, + int depth, + float minLineOffset ) { AtlasManager::AtlasSlot slot; std::vector< MeshRecord > meshContainer; - - if (mImageIds.Size() ) + Vector< Extent > extents; + TextCacheEntry textCacheEntry; + mDepth = depth; + + const Vector2& textSize( view.GetLayoutSize() ); + const Vector2 halfTextSize( textSize * 0.5f ); + const Vector2& shadowOffset( view.GetShadowOffset() ); + const Vector4& shadowColor( view.GetShadowColor() ); + const bool underlineEnabled( view.IsUnderlineEnabled() ); + const Vector4& underlineColor( view.GetUnderlineColor() ); + const float underlineHeight( view.GetUnderlineHeight() ); + + const bool useDefaultColor = ( NULL == colorsBuffer ); + + // Get the underline runs. + const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns(); + Vector underlineRuns; + underlineRuns.Resize( numberOfUnderlineRuns ); + view.GetUnderlineRuns( underlineRuns.Begin(), + 0u, + numberOfUnderlineRuns ); + + bool thereAreUnderlinedGlyphs = false; + + float currentUnderlinePosition = ZERO; + float currentUnderlineThickness = underlineHeight; + uint32_t currentBlockSize = 0; + FontId lastFontId = 0; + FontId lastUnderlinedFontId = 0; + Style style = STYLE_NORMAL; + + if ( fabsf( shadowOffset.x ) > Math::MACHINE_EPSILON_1 || fabsf( shadowOffset.y ) > Math::MACHINE_EPSILON_1 ) { - // Unreference any currently used glyphs - RemoveText(); + style = STYLE_DROP_SHADOW; } - // Set the block size to use, if an atlas is created - mGlyphManager.SetAtlasSize( DEFAULT_ATLAS_SIZE, CalculateBlockSize( glyphs ) ); + CalculateBlocksSize( glyphs ); - for ( uint32_t i = 0; i < glyphs.Size(); ++i ) + // Avoid emptying mTextCache (& removing references) until after incremented references for the new text + Vector< TextCacheEntry > newTextCache; + const GlyphInfo* const glyphsBuffer = glyphs.Begin(); + const Vector2* const positionsBuffer = positions.Begin(); + const Vector2 lineOffsetPosition( minLineOffset, 0.f ); + + for( uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i ) { - GlyphInfo glyph = glyphs[ i ]; + const GlyphInfo& glyph = *( glyphsBuffer + i ); + const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined( i, underlineRuns ); + thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph; // No operation for white space - if ( glyph.width && glyph.height ) + if( glyph.width && glyph.height ) { - Vector2 position = positions[ i ]; - MeshData newMeshData; - mGlyphManager.Cached( glyph.fontId, glyph.index, slot ); + // Are we still using the same fontId as previous + if( underlineGlyph && ( glyph.fontId != lastUnderlinedFontId ) ) + { + // We need to fetch fresh font underline metrics + FontMetrics fontMetrics; + mFontClient.GetFontMetrics( glyph.fontId, fontMetrics ); + currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) ); + const float descender = ceil( fabsf( fontMetrics.descender ) ); + + if( fabsf( underlineHeight ) < Math::MACHINE_EPSILON_1000 ) + { + currentUnderlineThickness = fontMetrics.underlineThickness; + + // Ensure underline will be at least a pixel high + if ( currentUnderlineThickness < ONE ) + { + currentUnderlineThickness = ONE; + } + else + { + currentUnderlineThickness = ceil( currentUnderlineThickness ); + } + } - if ( slot.mImageId ) + // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font + if( currentUnderlinePosition > descender ) + { + currentUnderlinePosition = descender; + } + + if( fabsf( currentUnderlinePosition ) < Math::MACHINE_EPSILON_1000 ) + { + // Move offset down by one ( EFL behavior ) + currentUnderlinePosition = ONE; + } + + lastUnderlinedFontId = glyph.fontId; + } // underline + + if( !mGlyphManager.IsCached( glyph.fontId, glyph.index, slot ) ) { - // This glyph already exists so generate mesh data plugging in our supplied position - mGlyphManager.GenerateMeshData( slot.mImageId, position, newMeshData ); - mImageIds.PushBack( slot.mImageId ); + // Select correct size for new atlas if needed....? + if( lastFontId != glyph.fontId ) + { + uint32_t index = 0u; + for( std::vector::const_iterator it = mBlockSizes.begin(), + endIt = mBlockSizes.end(); + it != endIt; + ++it, ++index ) + { + const MaxBlockSize& blockSize = *it; + if( blockSize.mFontId == glyph.fontId ) + { + currentBlockSize = index; + mGlyphManager.SetNewAtlasSize( DEFAULT_ATLAS_WIDTH, + DEFAULT_ATLAS_HEIGHT, + blockSize.mNeededBlockWidth, + blockSize.mNeededBlockHeight ); + } + } + } + + // Create a new image for the glyph + PixelData bitmap; + + // Whether the current glyph is a color one. + const bool isColorGlyph = mFontClient.IsColorGlyph( glyph.fontId, glyph.index ); + + // Retrieve the emoji's bitmap. + TextAbstraction::FontClient::GlyphBufferData glyphBufferData; + glyphBufferData.width = isColorGlyph ? glyph.width : 0; // Desired width and height. + glyphBufferData.height = isColorGlyph ? glyph.height : 0; + + mFontClient.CreateBitmap( glyph.fontId, + glyph.index, + glyphBufferData ); + + // Create the pixel data. + bitmap = PixelData::New( glyphBufferData.buffer, + glyph.width * glyph.height * GetBytesPerPixel( glyphBufferData.format ), + glyph.width, + glyph.height, + glyphBufferData.format, + PixelData::DELETE_ARRAY ); + + if( bitmap ) + { + MaxBlockSize& blockSize = mBlockSizes[currentBlockSize]; + + // Ensure that the next image will fit into the current block size + bool setSize = false; + if( bitmap.GetWidth() > blockSize.mNeededBlockWidth ) + { + setSize = true; + blockSize.mNeededBlockWidth = bitmap.GetWidth(); + } + if( bitmap.GetHeight() > blockSize.mNeededBlockHeight ) + { + setSize = true; + blockSize.mNeededBlockHeight = bitmap.GetHeight(); + } + + if( setSize ) + { + mGlyphManager.SetNewAtlasSize( DEFAULT_ATLAS_WIDTH, + DEFAULT_ATLAS_HEIGHT, + blockSize.mNeededBlockWidth, + blockSize.mNeededBlockHeight ); + } + + // Locate a new slot for our glyph + mGlyphManager.Add( glyph, bitmap, slot ); + } } else { - // Glyph doesn't currently exist in atlas so upload - BufferImage bitmap = mFontClient.CreateBitmap( glyph.fontId, glyph.index ); + // We have 2+ copies of the same glyph + mGlyphManager.AdjustReferenceCount( glyph.fontId, glyph.index, 1/*increment*/ ); + } - // Locate a new slot for our glyph - mGlyphManager.Add( glyph, bitmap, slot ); + // Move the origin (0,0) of the mesh to the center of the actor + const Vector2 position = *( positionsBuffer + i ) - halfTextSize - lineOffsetPosition; - // Generate mesh data for this quad, plugging in our supplied position - if ( slot.mImageId ) - { - mGlyphManager.GenerateMeshData( slot.mImageId, position, newMeshData ); - mImageIds.PushBack( slot.mImageId ); - } + // Generate mesh data for this quad, plugging in our supplied position + AtlasManager::Mesh2D newMesh; + mGlyphManager.GenerateMeshData( slot.mImageId, position, newMesh ); + textCacheEntry.mFontId = glyph.fontId; + textCacheEntry.mImageId = slot.mImageId; + textCacheEntry.mIndex = glyph.index; + newTextCache.PushBack( textCacheEntry ); + + AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin(); + + // Get the color of the character. + const ColorIndex colorIndex = useDefaultColor ? 0u : *( colorIndicesBuffer + i ); + const Vector4& color = ( useDefaultColor || ( 0u == colorIndex ) ) ? defaultColor : *( colorsBuffer + colorIndex - 1u ); + + for( unsigned int index = 0u, size = newMesh.mVertices.Count(); + index < size; + ++index ) + { + AtlasManager::Vertex2D& vertex = *( verticesBuffer + index ); + + // Set the color of the vertex. + vertex.mColor = color; } + // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas) - StitchTextMesh( meshContainer, newMeshData, slot ); + StitchTextMesh( meshContainer, + newMesh, + extents, + position.y + glyph.yBearing, + underlineGlyph, + currentUnderlinePosition, + currentUnderlineThickness, + slot ); + lastFontId = glyph.fontId; } + } // glyphs + + // Now remove references for the old text + RemoveText(); + mTextCache.Swap( newTextCache ); + + if( thereAreUnderlinedGlyphs ) + { + // Check to see if any of the text needs an underline + GenerateUnderlines( meshContainer, extents, underlineColor ); } // For each MeshData object, create a mesh actor and add to the renderable actor - if ( meshContainer.size() ) + if( !meshContainer.empty() ) { - for ( uint32_t i = 0; i < meshContainer.size(); ++i ) + if( !mActor ) { - Mesh mesh = Mesh::New( meshContainer[ i ].mMeshData ); - MeshActor actor = MeshActor::New( mesh ); - actor.SetParentOrigin( ParentOrigin::TOP_LEFT ); - actor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR );; + // Create a container actor to act as a common parent for text and shadow, to avoid color inheritence issues. + mActor = Actor::New(); + mActor.SetParentOrigin( ParentOrigin::TOP_LEFT ); + mActor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + mActor.SetSize( textSize ); + mActor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR ); + } - // Check to see what pixel format the shader should be - if ( mGlyphManager.GetPixelFormat( meshContainer[ i ].mAtlasId ) == Pixel::L8 ) - { - actor.SetShaderEffect( mBasicShader ); - } - else + for( std::vector< MeshRecord >::iterator it = meshContainer.begin(), + endIt = meshContainer.end(); + it != endIt; ++it ) + { + MeshRecord& meshRecord = *it; + + Actor actor = CreateMeshActor( textControl, animatablePropertyIndex, defaultColor, meshRecord, textSize, STYLE_NORMAL ); + + // Whether the actor has renderers. + const bool hasRenderer = actor.GetRendererCount() > 0u; + + // Create an effect if necessary + if( hasRenderer && + ( style == STYLE_DROP_SHADOW ) ) { - actor.SetShaderEffect( mBGRAShader ); + // Change the color of the vertices. + for( Vector::Iterator vIt = meshRecord.mMesh.mVertices.Begin(), + vEndIt = meshRecord.mMesh.mVertices.End(); + vIt != vEndIt; + ++vIt ) + { + AtlasManager::Vertex2D& vertex = *vIt; + + vertex.mColor = shadowColor; + } + + Actor shadowActor = CreateMeshActor(textControl, animatablePropertyIndex, defaultColor, meshRecord, textSize, STYLE_DROP_SHADOW ); +#if defined(DEBUG_ENABLED) + shadowActor.SetName( "Text Shadow renderable actor" ); +#endif + // Offset shadow in x and y + shadowActor.RegisterProperty("uOffset", shadowOffset ); + Dali::Renderer renderer( shadowActor.GetRendererAt( 0 ) ); + int depthIndex = renderer.GetProperty(Dali::Renderer::Property::DEPTH_INDEX); + renderer.SetProperty( Dali::Renderer::Property::DEPTH_INDEX, depthIndex - 1 ); + mActor.Add( shadowActor ); } - if ( i ) + if( hasRenderer ) { mActor.Add( actor ); } - else - { - mActor = actor; - } } - mActor.OffStageSignal().Connect( mSlotDelegate, &AtlasRenderer::Impl::OffStageDisconnect ); } +#if defined(DEBUG_ENABLED) + Toolkit::AtlasGlyphManager::Metrics metrics = mGlyphManager.GetMetrics(); + DALI_LOG_INFO( gLogFilter, Debug::General, "TextAtlasRenderer::GlyphManager::GlyphCount: %i, AtlasCount: %i, TextureMemoryUse: %iK\n", + metrics.mGlyphCount, + metrics.mAtlasMetrics.mAtlasCount, + metrics.mAtlasMetrics.mTextureMemoryUsed / 1024 ); + + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%s\n", metrics.mVerboseGlyphCounts.c_str() ); + + for( uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i ) + { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Atlas [%i] %sPixels: %s Size: %ix%i, BlockSize: %ix%i, BlocksUsed: %i/%i\n", + i + 1, i > 8 ? "" : " ", + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mPixelFormat == Pixel::L8 ? "L8 " : "BGRA", + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mWidth, + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mHeight, + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mBlockWidth, + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mSize.mBlockHeight, + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mBlocksUsed, + metrics.mAtlasMetrics.mAtlasMetrics[ i ].mTotalBlocks ); + } +#endif + } + + void RemoveText() + { + for( Vector< TextCacheEntry >::Iterator oldTextIter = mTextCache.Begin(); oldTextIter != mTextCache.End(); ++oldTextIter ) + { + mGlyphManager.AdjustReferenceCount( oldTextIter->mFontId, oldTextIter->mIndex, -1/*decrement*/ ); + } + mTextCache.Resize( 0 ); + } + + Actor CreateMeshActor( Actor textControl, Property::Index animatablePropertyIndex, const Vector4& defaultColor, const MeshRecord& meshRecord, + const Vector2& actorSize, Style style ) + { + PropertyBuffer quadVertices = PropertyBuffer::New( mQuadVertexFormat ); + quadVertices.SetData( const_cast< AtlasManager::Vertex2D* >( &meshRecord.mMesh.mVertices[ 0 ] ), meshRecord.mMesh.mVertices.Size() ); + + Geometry quadGeometry = Geometry::New(); + quadGeometry.AddVertexBuffer( quadVertices ); + quadGeometry.SetIndexBuffer( &meshRecord.mMesh.mIndices[0], meshRecord.mMesh.mIndices.Size() ); + + TextureSet textureSet( mGlyphManager.GetTextures( meshRecord.mAtlasId ) ); + + // Choose the shader to use. + const bool isColorShader = ( STYLE_DROP_SHADOW != style ) && ( Pixel::BGRA8888 == mGlyphManager.GetPixelFormat( meshRecord.mAtlasId ) ); + Shader shader; + if( isColorShader ) + { + // The glyph is an emoji and is not a shadow. + if( !mShaderRgba ) + { + mShaderRgba = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER_RGBA ); + } + shader = mShaderRgba; + } + else + { + // The glyph is text or a shadow. + if( !mShaderL8 ) + { + mShaderL8 = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER_L8 ); + } + shader = mShaderL8; + } + + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "defaultColor[%f, %f, %f, %f ]\n", defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.a ); + + Dali::Property::Index shaderTextColorIndex = shader.RegisterProperty( "textColorAnimatable", defaultColor ); + + if ( animatablePropertyIndex != Property::INVALID_INDEX ) + { + // create constraint for the animatable text's color Property with textColorAnimatable in the shader. + if( shaderTextColorIndex ) + { + Constraint constraint = Constraint::New( shader, shaderTextColorIndex, EqualToConstraint() ); + constraint.AddSource( Source( textControl, animatablePropertyIndex ) ); + constraint.Apply(); + } + } + else + { + // If not animating the text colour then set to 1's so shader uses the current vertex color + shader.RegisterProperty( "textColorAnimatable", Vector4(1.0, 1.0, 1.0, 1.0 ) ); + } + + Dali::Renderer renderer = Dali::Renderer::New( quadGeometry, shader ); + renderer.SetTextures( textureSet ); + renderer.SetProperty( Dali::Renderer::Property::BLEND_MODE, BlendMode::ON ); + renderer.SetProperty( Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT + mDepth ); + + Actor actor = Actor::New(); +#if defined(DEBUG_ENABLED) + actor.SetName( "Text renderable actor" ); +#endif + actor.AddRenderer( renderer ); + // Keep all of the origins aligned + actor.SetParentOrigin( ParentOrigin::TOP_LEFT ); + actor.SetAnchorPoint( AnchorPoint::TOP_LEFT ); + actor.SetSize( actorSize ); + actor.RegisterProperty("uOffset", Vector2::ZERO ); + actor.SetColorMode( USE_OWN_MULTIPLY_PARENT_COLOR ); + return actor; } void StitchTextMesh( std::vector< MeshRecord >& meshContainer, - MeshData& newMeshData, + AtlasManager::Mesh2D& newMesh, + Vector< Extent >& extents, + float baseLine, + bool underlineGlyph, + float underlinePosition, + float underlineThickness, AtlasManager::AtlasSlot& slot ) { if ( slot.mImageId ) { + float left = newMesh.mVertices[ 0 ].mPosition.x; + float right = newMesh.mVertices[ 1 ].mPosition.x; + // Check to see if there's a mesh data object that references the same atlas ? - for ( uint32_t i = 0; i < meshContainer.size(); ++i ) + uint32_t index = 0; + for ( std::vector< MeshRecord >::iterator mIt = meshContainer.begin(), + mEndIt = meshContainer.end(); + mIt != mEndIt; + ++mIt, ++index ) { - if ( slot.mAtlasId == meshContainer[ i ].mAtlasId ) + if( slot.mAtlasId == mIt->mAtlasId ) { - // Stitch the mesh to the existing mesh - mGlyphManager.StitchMesh( meshContainer[ i ].mMeshData, newMeshData ); + // Append the mesh to the existing mesh and adjust any extents + Toolkit::Internal::AtlasMeshFactory::AppendMesh( mIt->mMesh, newMesh ); + + if( underlineGlyph ) + { + AdjustExtents( extents, + meshContainer, + index, + left, + right, + baseLine, + underlinePosition, + underlineThickness ); + } + return; } } @@ -167,78 +654,243 @@ struct AtlasRenderer::Impl // No mesh data object currently exists that references this atlas, so create a new one MeshRecord meshRecord; meshRecord.mAtlasId = slot.mAtlasId; - meshRecord.mMeshData = newMeshData; + meshRecord.mMesh = newMesh; meshContainer.push_back( meshRecord ); + + if( underlineGlyph ) + { + // Adjust extents for this new meshrecord + AdjustExtents( extents, + meshContainer, + meshContainer.size() - 1u, + left, + right, + baseLine, + underlinePosition, + underlineThickness ); + } } } - // Unreference any glyphs that were used with this actor - void OffStageDisconnect( Dali::Actor actor ) + void AdjustExtents( Vector< Extent >& extents, + std::vector< MeshRecord>& meshRecords, + uint32_t index, + float left, + float right, + float baseLine, + float underlinePosition, + float underlineThickness ) { - RemoveText(); - } + bool foundExtent = false; + for ( Vector< Extent >::Iterator eIt = extents.Begin(), + eEndIt = extents.End(); + eIt != eEndIt; + ++eIt ) + { + if ( Equals( baseLine, eIt->mBaseLine ) ) + { + foundExtent = true; + if ( left < eIt->mLeft ) + { + eIt->mLeft = left; + } + if ( right > eIt->mRight ) + { + eIt->mRight = right; + } - void RemoveText() - { - for ( uint32_t i = 0; i < mImageIds.Size(); ++i ) + if ( underlinePosition > eIt->mUnderlinePosition ) + { + eIt->mUnderlinePosition = underlinePosition; + } + if ( underlineThickness > eIt->mUnderlineThickness ) + { + eIt->mUnderlineThickness = underlineThickness; + } + } + } + if ( !foundExtent ) { - mGlyphManager.Remove( mImageIds[ i ] ); + Extent extent; + extent.mLeft = left; + extent.mRight = right; + extent.mBaseLine = baseLine; + extent.mUnderlinePosition = underlinePosition; + extent.mUnderlineThickness = underlineThickness; + extent.mMeshRecordIndex = index; + extents.PushBack( extent ); } - mImageIds.Resize( 0 ); } - Vector2 CalculateBlockSize( const Vector& glyphs ) + void CalculateBlocksSize( const Vector& glyphs ) { - float maxWidth = glyphs[ 0 ].width; - float maxHeight = glyphs[ 0 ].height; - - for ( uint32_t i = 1u; i < glyphs.Size(); ++i ) + for( Vector::ConstIterator glyphIt = glyphs.Begin(), + glyphEndIt = glyphs.End(); + glyphIt != glyphEndIt; + ++glyphIt ) { - if ( maxWidth < glyphs[ i ].width ) + const FontId fontId = (*glyphIt).fontId; + bool foundFont = false; + + for( std::vector< MaxBlockSize >::const_iterator blockIt = mBlockSizes.begin(), + blockEndIt = mBlockSizes.end(); + blockIt != blockEndIt; + ++blockIt ) { - maxWidth = glyphs[ i ].width; + if( (*blockIt).mFontId == fontId ) + { + foundFont = true; + break; + } } - if ( maxHeight < glyphs[ i ].height ) + + if ( !foundFont ) { - maxHeight = glyphs[ i ].height; + FontMetrics fontMetrics; + mFontClient.GetFontMetrics( fontId, fontMetrics ); + + MaxBlockSize maxBlockSize; + maxBlockSize.mNeededBlockWidth = static_cast< uint32_t >( fontMetrics.height ); + maxBlockSize.mNeededBlockHeight = maxBlockSize.mNeededBlockWidth; + maxBlockSize.mFontId = fontId; + + mBlockSizes.push_back( maxBlockSize ); } } - return Vector2( maxWidth + PADDING.x, maxHeight + PADDING.y ); } - RenderableActor mActor; ///< The actor parent which renders the text + void GenerateUnderlines( std::vector< MeshRecord >& meshRecords, + Vector< Extent >& extents, + const Vector4& underlineColor ) + { + AtlasManager::Mesh2D newMesh; + unsigned short faceIndex = 0; + for ( Vector< Extent >::ConstIterator eIt = extents.Begin(), + eEndIt = extents.End(); + eIt != eEndIt; + ++eIt ) + { + AtlasManager::Vertex2D vert; + uint32_t index = eIt->mMeshRecordIndex; + Vector2 uv = mGlyphManager.GetAtlasSize( meshRecords[ index ].mAtlasId ); + + // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas ) + float u = HALF / uv.x; + float v = HALF / uv.y; + float thickness = eIt->mUnderlineThickness; + float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ( thickness * HALF ); + float tlx = eIt->mLeft; + float brx = eIt->mRight; + + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = ZERO; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack( vert ); + + vert.mPosition.x = brx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack( vert ); + + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = v; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack( vert ); + + vert.mPosition.x = brx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack( vert ); + + // Six indices in counter clockwise winding + newMesh.mIndices.PushBack( faceIndex + 1u ); + newMesh.mIndices.PushBack( faceIndex ); + newMesh.mIndices.PushBack( faceIndex + 2u ); + newMesh.mIndices.PushBack( faceIndex + 2u ); + newMesh.mIndices.PushBack( faceIndex + 3u ); + newMesh.mIndices.PushBack( faceIndex + 1u ); + faceIndex += 4; + + Toolkit::Internal::AtlasMeshFactory::AppendMesh( meshRecords[ index ].mMesh, newMesh ); + } + } + + Actor mActor; ///< The actor parent which renders the text AtlasGlyphManager mGlyphManager; ///< Glyph Manager to handle upload and caching - Vector< uint32_t > mImageIds; ///< A list of imageIDs used by the renderer - TextAbstraction::FontClient mFontClient; ///> The font client used to supply glyph information - SlotDelegate< AtlasRenderer::Impl > mSlotDelegate; ///> Signal generated to unreference glyphs when renderable actor is removed - ShaderEffect mBasicShader; ///> Shader to render L8 glyphs - ShaderEffect mBGRAShader; ///> Shader to render BGRA glyphs + TextAbstraction::FontClient mFontClient; ///< The font client used to supply glyph information + Shader mShaderL8; ///< The shader for glyphs and emoji's shadows. + Shader mShaderRgba; ///< The shader for emojis. + std::vector< MaxBlockSize > mBlockSizes; ///< Maximum size needed to contain a glyph in a block within a new atlas + Vector< TextCacheEntry > mTextCache; ///< Caches data from previous render + Property::Map mQuadVertexFormat; ///< Describes the vertex format for text + int mDepth; ///< DepthIndex passed by control when connect to stage }; Text::RendererPtr AtlasRenderer::New() { + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Text::AtlasRenderer::New()\n" ); + return Text::RendererPtr( new AtlasRenderer() ); } -RenderableActor AtlasRenderer::Render( Text::ViewInterface& view ) +Actor AtlasRenderer::Render( Text::ViewInterface& view, + Actor textControl, + Property::Index animatablePropertyIndex, + float& alignmentOffset, + int depth ) { + DALI_LOG_INFO( gLogFilter, Debug::General, "Text::AtlasRenderer::Render()\n" ); UnparentAndReset( mImpl->mActor ); - Text::Length numberOfGlyphs = view.GetNumberOfGlyphs(); + Length numberOfGlyphs = view.GetNumberOfGlyphs(); - if( numberOfGlyphs > 0 ) + if( numberOfGlyphs > 0u ) { Vector glyphs; glyphs.Resize( numberOfGlyphs ); - view.GetGlyphs( &glyphs[0], 0, numberOfGlyphs ); + Vector positions; + positions.Resize( numberOfGlyphs ); + + numberOfGlyphs = view.GetGlyphs( glyphs.Begin(), + positions.Begin(), + alignmentOffset, + 0u, + numberOfGlyphs ); - std::vector positions; - positions.resize( numberOfGlyphs ); - view.GetGlyphPositions( &positions[0], 0, numberOfGlyphs ); - mImpl->AddGlyphs( positions, glyphs ); + glyphs.Resize( numberOfGlyphs ); + positions.Resize( numberOfGlyphs ); + + const Vector4* const colorsBuffer = view.GetColors(); + const ColorIndex* const colorIndicesBuffer = view.GetColorIndices(); + const Vector4& defaultColor = view.GetTextColor(); + + mImpl->AddGlyphs( view, + textControl, + animatablePropertyIndex, + positions, + glyphs, + defaultColor, + colorsBuffer, + colorIndicesBuffer, + depth, + alignmentOffset ); + + /* In the case where AddGlyphs does not create a renderable Actor for example when glyphs are all whitespace create a new Actor. */ + /* This renderable actor is used to position the text, other "decorations" can rely on there always being an Actor regardless of it is whitespace or regular text. */ + if ( !mImpl->mActor ) + { + mImpl->mActor = Actor::New(); + } } + return mImpl->mActor; } @@ -250,5 +902,6 @@ AtlasRenderer::AtlasRenderer() AtlasRenderer::~AtlasRenderer() { + mImpl->RemoveText(); delete mImpl; -} \ No newline at end of file +}