From: Richard Huang Date: Tue, 27 Jun 2017 15:37:17 +0000 (+0100) Subject: Refactor TextLabel to use text visual X-Git-Tag: dali_1.2.53~2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=3a23cbcd64ab5780928e4a141e497242c9989110;hp=b134e246fb1cd7d1f31f7cca5bfa33b6a559d7be Refactor TextLabel to use text visual Change-Id: Ia3f55571721cd988623200fb36bc47e1ee06f932 --- diff --git a/dali-toolkit/devel-api/visuals/text-visual-properties.h b/dali-toolkit/devel-api/visuals/text-visual-properties.h index dfb91ec..4286c38 100644 --- a/dali-toolkit/devel-api/visuals/text-visual-properties.h +++ b/dali-toolkit/devel-api/visuals/text-visual-properties.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_VISUAL_PROPERTIES_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -88,6 +88,18 @@ enum * @details name "enableMarkup", type BOOLEAN */ ENABLE_MARKUP, + + /** + * @brief The shadow parameters. + * @details name "shadow", type MAP. + */ + SHADOW, + + /** + * @brief The default underline parameters. + * @details name "underline", type MAP. + */ + UNDERLINE, }; } // namespace Property diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp index a6fa7ea..3778d1b 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.cpp @@ -35,6 +35,13 @@ #include #include +#include +#include +#include +#include +#include +#include + using namespace Dali::Toolkit::Text; namespace Dali @@ -97,7 +104,7 @@ BaseHandle Create() // Setup properties, signals and actions using the type-registry. DALI_TYPE_REGISTRATION_BEGIN( Toolkit::TextLabel, Toolkit::Control, Create ); -DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "renderingBackend", INTEGER, RENDERING_BACKEND ) +DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "renderingBackend", INTEGER, RENDERING_BACKEND ) // Deprecated property DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "text", STRING, TEXT ) DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "fontFamily", STRING, FONT_FAMILY ) DALI_PROPERTY_REGISTRATION( Toolkit, TextLabel, "fontStyle", MAP, FONT_STYLE ) @@ -127,7 +134,7 @@ DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextLabel, "autoScrollLoopDelay", FLO DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextLabel, "autoScrollStopMode", STRING, AUTO_SCROLL_STOP_MODE ) DALI_DEVEL_PROPERTY_REGISTRATION_READ_ONLY( Toolkit, TextLabel, "lineCount", INTEGER, LINE_COUNT ) DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextLabel, "lineWrapMode", STRING, LINE_WRAP_MODE ) -DALI_DEVEL_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT( Toolkit, TextLabel, "textColorAnimatable", Color::WHITE, TEXT_COLOR_ANIMATABLE ) +DALI_DEVEL_ANIMATABLE_PROPERTY_REGISTRATION_WITH_DEFAULT( Toolkit, TextLabel, "textColorAnimatable", Color::BLACK, TEXT_COLOR_ANIMATABLE ) DALI_TYPE_REGISTRATION_END() } // namespace @@ -158,6 +165,8 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr { case Toolkit::TextLabel::Property::RENDERING_BACKEND: { + DALI_LOG_WARNING("[%s] Using deprecated Property TextLabel::Property::RENDERING_BACKEND which is no longer supported and will be ignored\n", __FUNCTION__); + int backend = value.Get< int >(); #ifndef ENABLE_VECTOR_BASED_TEXT_RENDERING @@ -169,7 +178,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if( impl.mRenderingBackend != backend ) { impl.mRenderingBackend = backend; - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; if( impl.mController ) { @@ -258,7 +267,8 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr case Toolkit::TextLabel::Property::TEXT_COLOR: { - SetProperty( object, DevelTextLabel::Property::TEXT_COLOR_ANIMATABLE, value ); + label.SetProperty( DevelTextLabel::Property::TEXT_COLOR_ANIMATABLE, value ); + impl.mTextUpdateNeeded = true; break; } @@ -270,7 +280,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if ( impl.mController->GetShadowOffset() != shadowOffset ) { impl.mController->SetShadowOffset( shadowOffset ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } } break; @@ -283,7 +293,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if ( impl.mController->GetShadowColor() != shadowColor ) { impl.mController->SetShadowColor( shadowColor ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } } break; @@ -296,7 +306,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if ( impl.mController->GetUnderlineColor() != color ) { impl.mController->SetUnderlineColor( color ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } } break; @@ -309,7 +319,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if ( impl.mController->IsUnderlineEnabled() != enabled ) { impl.mController->SetUnderlineEnabled( enabled ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } } break; @@ -323,7 +333,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr if( fabsf( impl.mController->GetUnderlineHeight() - height ) > Math::MACHINE_EPSILON_1000 ) { impl.mController->SetUnderlineHeight( height ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } } break; @@ -421,7 +431,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr { const float lineSpacing = value.Get(); impl.mController->SetDefaultLineSpacing( lineSpacing ); - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } break; } @@ -430,7 +440,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr const bool update = SetUnderlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ); if( update ) { - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } break; } @@ -439,7 +449,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr const bool update = SetShadowProperties( impl.mController, value, Text::EffectStyle::DEFAULT ); if( update ) { - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } break; } @@ -448,7 +458,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr const bool update = SetEmbossProperties( impl.mController, value, Text::EffectStyle::DEFAULT ); if( update ) { - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } break; } @@ -457,7 +467,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr const bool update = SetOutlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ); if( update ) { - impl.mRenderer.Reset(); + impl.mTextUpdateNeeded = true; } break; } @@ -518,6 +528,8 @@ Property::Value TextLabel::GetProperty( BaseObject* object, Property::Index inde { case Toolkit::TextLabel::Property::RENDERING_BACKEND: { + DALI_LOG_WARNING("[%s] Using deprecated Property TextLabel::Property::RENDERING_BACKEND which is no longer supported and will be ignored\n", __FUNCTION__); + value = impl.mRenderingBackend; break; } @@ -780,11 +792,19 @@ void TextLabel::OnInitialize() { Actor self = Self(); - mController = Text::Controller::New( this ); + Property::Map propertyMap; + propertyMap.Add( Toolkit::Visual::Property::TYPE, Toolkit::DevelVisual::TEXT ); - // When using the vector-based rendering, the size of the GLyphs are different - TextAbstraction::GlyphType glyphType = (Text::RENDERING_VECTOR_BASED == mRenderingBackend) ? TextAbstraction::VECTOR_GLYPH : TextAbstraction::BITMAP_GLYPH; - mController->SetGlyphType( glyphType ); + mVisual = Toolkit::VisualFactory::Get().CreateVisual( propertyMap ); + DevelControl::RegisterVisual( *this, Toolkit::TextLabel::Property::TEXT, mVisual ); + + TextVisual::SetAnimatableTextColorProperty( mVisual, Toolkit::DevelTextLabel::Property::TEXT_COLOR_ANIMATABLE ); + + mController = TextVisual::GetController(mVisual); + if( mController ) + { + mController->SetControlInterface(this); + } // Use height-for-width negotiation by default self.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::WIDTH ); @@ -795,8 +815,6 @@ void TextLabel::OnInitialize() Layout::Engine& engine = mController->GetLayoutEngine(); engine.SetCursorWidth( 0u ); // Do not layout space for the cursor. - - self.OnStageSignal().Connect( this, &TextLabel::OnStageConnect ); } void TextLabel::OnStyleChange( Toolkit::StyleManager styleManager, StyleChange::Type change ) @@ -849,14 +867,13 @@ void TextLabel::OnPropertySet( Property::Index index, Property::Value propertyVa switch ( index ) { - case Toolkit::TextLabel::Property::TEXT_COLOR: case Toolkit::DevelTextLabel::Property::TEXT_COLOR_ANIMATABLE: { const Vector4& textColor = propertyValue.Get< Vector4 >(); if( mController->GetDefaultColor() != textColor ) { mController->SetDefaultColor( textColor ); - mRenderer.Reset(); + mTextUpdateNeeded = true; } break; } @@ -876,76 +893,50 @@ void TextLabel::OnRelayout( const Vector2& size, RelayoutContainer& container ) Self().GetPadding( padding ); Vector2 contentSize( size.x - ( padding.left + padding.right ), size.y - ( padding.top + padding.bottom ) ); - const Text::Controller::UpdateTextType updateTextType = mController->Relayout( contentSize ); - if( ( Text::Controller::NONE_UPDATED != ( Text::Controller::MODEL_UPDATED & updateTextType ) ) || - !mRenderer ) - { - if( !mRenderer ) - { - mRenderer = Text::Backend::Get().NewRenderer( mRenderingBackend ); - } - RenderText(); - } -} - -void TextLabel::RequestTextRelayout() -{ - RelayoutRequest(); -} - -void TextLabel::RenderText() -{ - DALI_LOG_INFO( gLogFilter, Debug::General, "TextLabel::RenderText IsAutoScrollEnabled[%s] [%p]\n", ( mController->IsAutoScrollEnabled())?"true":"false", this ); - - Actor self = Self(); - Actor renderableActor; - - float alignmentOffset = 0.f; - if( mRenderer ) + if( ( Text::Controller::NONE_UPDATED != ( Text::Controller::MODEL_UPDATED & updateTextType ) ) + || mTextUpdateNeeded ) { + DALI_LOG_INFO( gLogFilter, Debug::General, "TextLabel::OnRelayout IsAutoScrollEnabled[%s] [%p]\n", ( mController->IsAutoScrollEnabled())?"true":"false", this ); - Dali::Toolkit::TextLabel handle = Dali::Toolkit::TextLabel( GetOwner() ); - - renderableActor = mRenderer->Render( mController->GetView(), - handle, - Toolkit::DevelTextLabel::Property::TEXT_COLOR_ANIMATABLE, - alignmentOffset, - DepthIndex::CONTENT ); - } + // Update the visual + TextVisual::EnableRendererUpdate( mVisual ); - if( renderableActor != mRenderableActor ) - { - UnparentAndReset( mRenderableActor ); + Padding padding; + Self().GetPadding( padding ); - if( renderableActor ) - { - const Vector2& scrollOffset = mController->GetTextModel()->GetScrollPosition(); - Padding padding; - self.GetPadding( padding ); - renderableActor.SetPosition( scrollOffset.x + alignmentOffset + padding.left, scrollOffset.y + padding.top ); - - self.Add( renderableActor ); - } - mRenderableActor = renderableActor; + Property::Map visualTransform; + visualTransform.Add( Toolkit::DevelVisual::Transform::Property::SIZE, contentSize ) + .Add( Toolkit::DevelVisual::Transform::Property::SIZE_POLICY, Vector2( DevelVisual::Transform::Policy::ABSOLUTE, DevelVisual::Transform::Policy::ABSOLUTE ) ) + .Add( Toolkit::DevelVisual::Transform::Property::OFFSET, Vector2(padding.left, padding.top) ) + .Add( Toolkit::DevelVisual::Transform::Property::OFFSET_POLICY, Vector2( Toolkit::DevelVisual::Transform::Policy::ABSOLUTE, Toolkit::DevelVisual::Transform::Policy::ABSOLUTE ) ) + .Add( Toolkit::DevelVisual::Transform::Property::ORIGIN, Toolkit::Align::TOP_BEGIN ) + .Add( Toolkit::DevelVisual::Transform::Property::ANCHOR_POINT, Toolkit::Align::TOP_BEGIN ); + mVisual.SetTransformAndSize( visualTransform, size ); if ( mController->IsAutoScrollEnabled() ) { SetUpAutoScrolling(); } + + mTextUpdateNeeded = false; } } +void TextLabel::RequestTextRelayout() +{ + RelayoutRequest(); +} + void TextLabel::SetUpAutoScrolling() { const Size& controlSize = mController->GetView().GetControlSize(); - const Size offScreenSize = GetNaturalSize().GetVectorXY(); // As relayout of text may not be done at this point natural size is used to get size. Single line scrolling only. - const float alignmentOffset = mController->GetAutoScrollLineAlignment(); + const Size textNaturalSize = GetNaturalSize().GetVectorXY(); // As relayout of text may not be done at this point natural size is used to get size. Single line scrolling only. const Text::CharacterDirection direction = mController->GetAutoScrollDirection(); - DALI_LOG_INFO( gLogFilter, Debug::General, "TextLabel::SetUpAutoScrolling alignmentOffset[%f] offScreenSize[%f,%f] controlSize[%f,%f]\n", - alignmentOffset, offScreenSize.x,offScreenSize.y , controlSize.x,controlSize.y ); + DALI_LOG_INFO( gLogFilter, Debug::General, "TextLabel::SetUpAutoScrolling textNaturalSize[%f,%f] controlSize[%f,%f]\n", + textNaturalSize.x,textNaturalSize.y , controlSize.x,controlSize.y ); if ( !mTextScroller ) { @@ -954,23 +945,27 @@ void TextLabel::SetUpAutoScrolling() // If speed, loopCount or gap not set via property system then will need to create a TextScroller with defaults mTextScroller = Text::TextScroller::New( *this ); } - mTextScroller->SetParameters( mRenderableActor, controlSize, offScreenSize, direction, alignmentOffset, mController->GetHorizontalAlignment() ); - - Actor self = Self(); - self.Add( mTextScroller->GetScrollingText() ); - self.Add( mTextScroller->GetSourceCamera() ); -} -void TextLabel::OnStageConnect( Dali::Actor actor ) -{ - if ( mHasBeenStaged ) - { - RenderText(); - } - else - { - mHasBeenStaged = true; - } + // Create a texture of the text for scrolling + Text::TypesetterPtr typesetter = Text::Typesetter::New( mController->GetTextModel() ); + PixelData data = typesetter->Render( textNaturalSize, Text::Typesetter::RENDER_TEXT_AND_STYLES, true ); // ignore the horizontal alignment + Texture texture = Texture::New( Dali::TextureType::TEXTURE_2D, + data.GetPixelFormat(), + data.GetWidth(), + data.GetHeight() ); + texture.Upload( data ); + + TextureSet textureSet = TextureSet::New(); + textureSet.SetTexture( 0u, texture ); + + // Filter mode needs to be set to nearest to avoid blurry text. + Sampler sampler = Sampler::New(); + sampler.SetFilterMode( FilterMode::NEAREST, FilterMode::NEAREST ); + textureSet.SetSampler( 0u, sampler ); + + // Set parameters for scrolling + Renderer renderer = static_cast( GetImplementation( mVisual ) ).GetRenderer(); + mTextScroller->SetParameters( Self(), renderer, textureSet, controlSize, textNaturalSize, direction, mController->GetHorizontalAlignment(), mController->GetVerticalAlignment() ); } void TextLabel::ScrollingFinished() @@ -985,7 +980,7 @@ void TextLabel::ScrollingFinished() TextLabel::TextLabel() : Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ), mRenderingBackend( DEFAULT_RENDERING_BACKEND ), - mHasBeenStaged( false ) + mTextUpdateNeeded( false ) { } diff --git a/dali-toolkit/internal/controls/text-controls/text-label-impl.h b/dali-toolkit/internal/controls/text-controls/text-label-impl.h index f15acbf..85e94ab 100644 --- a/dali-toolkit/internal/controls/text-controls/text-label-impl.h +++ b/dali-toolkit/internal/controls/text-controls/text-label-impl.h @@ -18,6 +18,9 @@ * */ +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include @@ -26,6 +29,8 @@ #include #include #include +#include + namespace Dali { @@ -132,14 +137,6 @@ private: TextLabel(const TextLabel&); TextLabel& operator=(const TextLabel& rhs); - // Connection needed to re-render text, when a Text Label returns to the stage - void OnStageConnect( Dali::Actor actor ); - - /** - * @brief Render view, create and attach actor(s) to this Text Label - */ - void RenderText(); - /** * @brief Set up Autoscrolling */ @@ -148,11 +145,12 @@ private: private: // Data Text::ControllerPtr mController; - Text::RendererPtr mRenderer; Text::TextScrollerPtr mTextScroller; - Actor mRenderableActor; + + Toolkit::Visual::Base mVisual; + int mRenderingBackend; - bool mHasBeenStaged:1; + bool mTextUpdateNeeded:1; }; } // namespace Internal diff --git a/dali-toolkit/internal/text/rendering/text-typesetter.cpp b/dali-toolkit/internal/text/rendering/text-typesetter.cpp index a55fd8d..11cde00 100644 --- a/dali-toolkit/internal/text/rendering/text-typesetter.cpp +++ b/dali-toolkit/internal/text/rendering/text-typesetter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -21,6 +21,7 @@ // EXTERNAL INCLUDES #include #include +#include // INTERNAL INCLUDES #include @@ -42,7 +43,7 @@ namespace */ struct GlyphData { - uint32_t* bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. + Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. Vector2* position; ///< The position of the glyph. TextAbstraction::FontClient::GlyphBufferData glyphBitmap; ///< The glyph's bitmap. unsigned int width; ///< The bitmap's width. @@ -57,10 +58,12 @@ struct GlyphData * @param[in] data Struct which contains the glyph's data and the bitmap's data. * @param[in] position The position of the glyph. * @param[in] color The color of the glyph. + * @param[in] style The style of the text. */ -void TypesetGlyph( const GlyphData& data, +void TypesetGlyph( GlyphData& data, const Vector2* const position, - const Vector4* const color ) + const Vector4* const color, + Typesetter::Style style) { if( ( 0u == data.glyphBitmap.width ) || ( 0u == data.glyphBitmap.height ) ) { @@ -110,18 +113,39 @@ void TypesetGlyph( const GlyphData& data, break; } + uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( data.bitmapBuffer.GetBuffer() ); + if( isColorGlyph ) { - // Retrieves the color from the glyph. The format is BGRA8888. + // Retrieves the color from the color glyph. The format is BGRA8888. uint32_t packedColorGlyph = *( colorGlyphBuffer + glyphBufferOffset + index ); + uint8_t* packedColorGlyphBuffer = reinterpret_cast( &packedColorGlyph ); + + if( Typesetter::STYLE_SHADOW == style ) + { + // The shadow of color glyph needs to have the shadow color. + *( packedColorGlyphBuffer + 2 ) = static_cast( color->b * 255.f ); + *( packedColorGlyphBuffer + 1 ) = static_cast( color->g * 255.f ); + *packedColorGlyphBuffer = static_cast( color->r * 255.f ); + } + else + { + std::swap( *packedColorGlyphBuffer, *( packedColorGlyphBuffer + 2u ) ); // Swap B and R. + } // Update the alpha channel. - uint8_t* packedColorGlyphBuffer = reinterpret_cast( &packedColorGlyph ); - std::swap( *packedColorGlyphBuffer, *( packedColorGlyphBuffer + 2u ) ); // Swap B and R. - *( packedColorGlyphBuffer + 3u ) = static_cast( color->a * static_cast( *( packedColorGlyphBuffer + 3u ) ) ); + if( Typesetter::STYLE_MASK == style ) + { + // Create an alpha mask for color glyph. + *( packedColorGlyphBuffer + 3u ) = 0u; + } + else + { + *( packedColorGlyphBuffer + 3u ) = static_cast( color->a * static_cast( *( packedColorGlyphBuffer + 3u ) ) ); + } // Set the color into the final pixel buffer. - *( data.bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColorGlyph; + *( bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColorGlyph; } else { @@ -130,12 +154,31 @@ void TypesetGlyph( const GlyphData& data, *( packedColorBuffer + 3u ) = static_cast( color->a * static_cast( alpha ) ); // Set the color into the final pixel buffer. - *( data.bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColor; + *( bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColor; } } } } +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; +} + } // namespace TypesetterPtr Typesetter::New( const ModelInterface* const model ) @@ -148,7 +191,7 @@ ViewModel* Typesetter::GetViewModel() return mModel; } -PixelData Typesetter::Render( const Vector2& size ) +PixelData Typesetter::Render( const Vector2& size, RenderBehaviour behaviour, bool ignoreHorizontalAlignment ) { // @todo. This initial implementation for a TextLabel has only one visible page. @@ -180,6 +223,73 @@ PixelData Typesetter::Render( const Vector2& size ) } } + // Generate the image buffers of the text for each different style first, + // then combine all of them together as one final image buffer. We try to + // do all of these in CPU only, so that once the final texture is generated, + // no calculation is needed in GPU during each frame. + + const unsigned int bufferWidth = static_cast( size.width ); + const unsigned int bufferHeight = static_cast( size.height ); + + const unsigned int bufferSizeInt = bufferWidth * bufferHeight; + const unsigned int bufferSizeChar = 4u * bufferSizeInt; + + Length numberOfGlyphs = mModel->GetNumberOfGlyphs(); + + Devel::PixelBuffer imageBuffer; + + if( RENDER_MASK == behaviour ) + { + // Generate the image buffer as an alpha mask for color glyphs. + imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs - 1 ); + } + else if( RENDER_NO_TEXT == behaviour ) + { + // Generate an empty image buffer so that it can been combined with the image buffers for styles + imageBuffer = Devel::PixelBuffer::New( bufferWidth, bufferHeight, Pixel::RGBA8888 ); + memset( imageBuffer.GetBuffer(), 0u, bufferSizeChar ); + } + else + { + // Generate the image buffer for the text with no style. + imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs -1 ); + } + + if ( ( RENDER_NO_STYLES != behaviour ) && ( RENDER_MASK != behaviour ) ) + { + // @todo. Support shadow and underline for partial text later on. + + // Generate the shadow if enabled + const Vector2& shadowOffset = mModel->GetShadowOffset(); + if ( fabsf( shadowOffset.x ) > Math::MACHINE_EPSILON_1 || fabsf( shadowOffset.y ) > Math::MACHINE_EPSILON_1 ) + { + // Create the image buffer for shadow + Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs - 1 ); + + // Combine the two buffers + imageBuffer = CombineImageBuffer( imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight ); + } + + // Generate the underline if enabled + const bool underlineEnabled = mModel->IsUnderlineEnabled(); + if ( underlineEnabled ) + { + // Create the image buffer for underline + Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs - 1 ); + + // Combine the two buffers + imageBuffer = CombineImageBuffer( imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight ); + } + } + + // Create the final PixelData for the combined image buffer + PixelData pixelData = Devel::PixelBuffer::Convert( imageBuffer ); + + return pixelData; +} + +Devel::PixelBuffer Typesetter::CreateImageBuffer( const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, int verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex ) +{ // Retrieve lines, glyphs, positions and colors from the view model. const Length modelNumberOfLines = mModel->GetNumberOfLines(); const LineRun* const modelLinesBuffer = mModel->GetLines(); @@ -190,26 +300,19 @@ PixelData Typesetter::Render( const Vector2& size ) const ColorIndex* const colorIndexBuffer = mModel->GetColorIndices(); // Whether to use the default color. - const bool useDefaultColor = NULL == colorsBuffer; + const bool useDefaultColor = ( NULL == colorsBuffer ); const Vector4& defaultColor = mModel->GetDefaultColor(); // Create and initialize the pixel buffer. GlyphData glyphData; - glyphData.verticalOffset = penY; + glyphData.verticalOffset = verticalOffset; - glyphData.width = static_cast( size.width ); - glyphData.height = static_cast( size.height ); - const unsigned int bufferSizeInt = glyphData.width * glyphData.height; + glyphData.width = bufferWidth; + glyphData.height = bufferHeight; + const unsigned int bufferSizeInt = bufferWidth * bufferHeight; const unsigned int bufferSizeChar = 4u * bufferSizeInt; - glyphData.bitmapBuffer = new uint32_t[ bufferSizeInt ]; // This array will get deleted by PixelData because of the DELETE_ARRAY parameter. - memset( glyphData.bitmapBuffer, 0u, bufferSizeChar ); - - PixelData pixelData = PixelData::New( reinterpret_cast( glyphData.bitmapBuffer ), - bufferSizeChar, - glyphData.width, - glyphData.height, - Pixel::RGBA8888, // The format is RGBA8888 because is the format accepted by the image atlas manager. - PixelData::DELETE_ARRAY ); + glyphData.bitmapBuffer = Devel::PixelBuffer::New( bufferWidth, bufferHeight, Pixel::RGBA8888 ); + memset( glyphData.bitmapBuffer.GetBuffer(), 0u, bufferSizeChar ); // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs. TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get(); @@ -220,15 +323,54 @@ PixelData Typesetter::Render( const Vector2& size ) const LineRun& line = *( modelLinesBuffer + lineIndex ); // Sets the horizontal offset of the line. - glyphData.horizontalOffset = static_cast( line.alignmentOffset ); + glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast( line.alignmentOffset ); // Increases the vertical offset with the line's ascender. glyphData.verticalOffset += static_cast( line.ascender ); + if ( style == Typesetter::STYLE_SHADOW ) + { + const Vector2& shadowOffset = mModel->GetShadowOffset(); + glyphData.horizontalOffset += shadowOffset.x; + if ( lineIndex == 0u ) + { + // Only need to add the vertical shadow offset for once + glyphData.verticalOffset += shadowOffset.y; + } + } + + const bool underlineEnabled = mModel->IsUnderlineEnabled(); + const Vector4& underlineColor = mModel->GetUnderlineColor(); + const float underlineHeight = mModel->GetUnderlineHeight(); + + // Get the underline runs. + const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns(); + Vector underlineRuns; + underlineRuns.Resize( numberOfUnderlineRuns ); + mModel->GetUnderlineRuns( underlineRuns.Begin(), 0u, numberOfUnderlineRuns ); + + bool thereAreUnderlinedGlyphs = false; + + float currentUnderlinePosition = 0.0f; + float currentUnderlineThickness = underlineHeight; + float maxUnderlineThickness = currentUnderlineThickness; + + FontId lastUnderlinedFontId = 0; + + float lineExtentLeft = bufferWidth; + float lineExtentRight = 0.0f; + float baseline = 0.0f; + // Traverses the glyphs of the line. const GlyphIndex endGlyphIndex = std::min( numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs ); for( GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex ) { + if ( glyphIndex < fromGlyphIndex || glyphIndex > toGlyphIndex ) + { + // Ignore any glyph that out of the specified range + continue; + } + // Retrieve the glyph's info. const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex; @@ -239,12 +381,84 @@ PixelData Typesetter::Render( const Vector2& size ) continue; } + const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined( glyphIndex, underlineRuns ); + thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph; + + // Are we still using the same fontId as previous + if( underlineGlyph && ( glyphInfo->fontId != lastUnderlinedFontId ) ) + { + // We need to fetch fresh font underline metrics + FontMetrics fontMetrics; + fontClient.GetFontMetrics( glyphInfo->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 < 1.0f ) + { + currentUnderlineThickness = 1.0f; + } + else + { + currentUnderlineThickness = ceil( currentUnderlineThickness ); + } + } + + // The underline thickness should be the max underline thickness of all glyphs of the line. + if ( currentUnderlineThickness > maxUnderlineThickness ) + { + maxUnderlineThickness = currentUnderlineThickness; + } + + // 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 = 1.0f; + } + + lastUnderlinedFontId = glyphInfo->fontId; + } // underline + // Retrieves the glyph's position. const Vector2* const position = positionBuffer + glyphIndex; + if ( baseline < position->y + glyphInfo->yBearing ) + { + baseline = position->y + glyphInfo->yBearing; + } + + // Calculate the positions of leftmost and rightmost glyphs in the current line + if ( position->x < lineExtentLeft) + { + lineExtentLeft = position->x; + } + + if ( position->x + glyphInfo->width > lineExtentRight) + { + lineExtentRight = position->x + glyphInfo->width; + } // Retrieves the glyph's color. const ColorIndex colorIndex = *( colorIndexBuffer + glyphIndex ); - const Vector4* const color = ( useDefaultColor || ( 0u == colorIndex ) ) ? &defaultColor : colorsBuffer + ( colorIndex - 1u ); + + const Vector4* color; + if ( style == Typesetter::STYLE_SHADOW ) + { + color = &( mModel->GetShadowColor() ); + } + else + { + color = ( useDefaultColor || ( 0u == colorIndex ) ) ? &defaultColor : colorsBuffer + ( colorIndex - 1u ); + } // Retrieves the glyph's bitmap. glyphData.glyphBitmap.buffer = NULL; @@ -259,11 +473,47 @@ PixelData Typesetter::Render( const Vector2& size ) { TypesetGlyph( glyphData, position, - color ); + color, + style ); // delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer delete []glyphData.glyphBitmap.buffer; glyphData.glyphBitmap.buffer = NULL; + } + } + + // Draw the underline from the leftmost glyph to the rightmost glyph + if ( thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE ) + { + int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition; + for( unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++ ) + { + if( ( y < 0 ) || ( y > bufferHeight - 1 ) ) + { + // Do not write out of bounds. + break; + } + + for( unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ ) + { + if( ( x < 0 ) || ( x > bufferWidth - 1 ) ) + { + // Do not write out of bounds. + break; + } + + uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() ); + uint32_t underlinePixel = *( bitmapBuffer + y * glyphData.width + x ); + uint8_t* underlinePixelBuffer = reinterpret_cast( &underlinePixel ); + + // Write the underline color to the pixel buffer + *( underlinePixelBuffer ) = static_cast( underlineColor.r * 255.f ); + *( underlinePixelBuffer + 1u ) = static_cast( underlineColor.g * 255.f ); + *( underlinePixelBuffer + 2u ) = static_cast( underlineColor.b * 255.f ); + *( underlinePixelBuffer + 3u ) = static_cast( underlineColor.a * 255.f ); + + *( bitmapBuffer + y * glyphData.width + x ) = underlinePixel; + } } } @@ -271,7 +521,69 @@ PixelData Typesetter::Render( const Vector2& size ) glyphData.verticalOffset += static_cast( -line.descender ); } - return pixelData; + return glyphData.bitmapBuffer; +} + +Devel::PixelBuffer Typesetter::CombineImageBuffer( Devel::PixelBuffer topPixelBuffer, Devel::PixelBuffer bottomPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight ) +{ + unsigned char* topBuffer = topPixelBuffer.GetBuffer(); + unsigned char* bottomBuffer = bottomPixelBuffer.GetBuffer(); + + Devel::PixelBuffer combinedPixelBuffer; + + if ( topBuffer == NULL && bottomBuffer == NULL ) + { + // Nothing to do if both buffers are empty. + return combinedPixelBuffer; + } + + if ( topBuffer == NULL ) + { + // Nothing to do if topBuffer is empty. + return bottomPixelBuffer; + } + + if ( bottomBuffer == NULL ) + { + // Nothing to do if bottomBuffer is empty. + return topPixelBuffer; + } + + const unsigned int bufferSizeInt = bufferWidth * bufferHeight; + const unsigned int bufferSizeChar = 4u * bufferSizeInt; + + combinedPixelBuffer = Devel::PixelBuffer::New( bufferWidth, bufferHeight, Pixel::RGBA8888 ); + uint8_t* combinedBuffer = reinterpret_cast< uint8_t* >( combinedPixelBuffer.GetBuffer() ); + memset( combinedBuffer, 0u, bufferSizeChar ); + + for (unsigned int pixelIndex = 0; pixelIndex < bufferSizeInt; pixelIndex++) + { + // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels. + // Otherwise, copy pixel from topBuffer to combinedBuffer. + + unsigned int alphaBuffer1 = topBuffer[pixelIndex*4+3]; + unsigned int alphaBuffer2 = bottomBuffer[pixelIndex*4+3]; + + if ( alphaBuffer1 != 255 || alphaBuffer2 != 255 ) + { + // At least one pixel is not fully opaque + // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer + combinedBuffer[pixelIndex*4] = ( topBuffer[pixelIndex*4] * topBuffer[pixelIndex*4+3] / 255 ) + ( bottomBuffer[pixelIndex*4] * bottomBuffer[pixelIndex*4+3] * ( 255 - topBuffer[pixelIndex*4+3] ) / ( 255*255 ) ); + combinedBuffer[pixelIndex*4+1] = ( topBuffer[pixelIndex*4+1] * topBuffer[pixelIndex*4+3] / 255 ) + ( bottomBuffer[pixelIndex*4+1] * bottomBuffer[pixelIndex*4+3] * ( 255 - topBuffer[pixelIndex*4+3] ) / ( 255*255 ) ); + combinedBuffer[pixelIndex*4+2] = ( topBuffer[pixelIndex*4+2] * topBuffer[pixelIndex*4+3] / 255 ) + ( bottomBuffer[pixelIndex*4+2] * bottomBuffer[pixelIndex*4+3] * ( 255 - topBuffer[pixelIndex*4+3] ) / ( 255*255 ) ); + combinedBuffer[pixelIndex*4+3] = topBuffer[pixelIndex*4+3] + ( bottomBuffer[pixelIndex*4+3] * ( 255 - topBuffer[pixelIndex*4+3] ) / 255 ); + } + else + { + // Copy the pixel from topBuffer to combinedBuffer + combinedBuffer[pixelIndex*4] = topBuffer[pixelIndex*4]; + combinedBuffer[pixelIndex*4+1] = topBuffer[pixelIndex*4+1]; + combinedBuffer[pixelIndex*4+2] = topBuffer[pixelIndex*4+2]; + combinedBuffer[pixelIndex*4+3] = topBuffer[pixelIndex*4+3]; + } + } + + return combinedPixelBuffer; } Typesetter::Typesetter( const ModelInterface* const model ) diff --git a/dali-toolkit/internal/text/rendering/text-typesetter.h b/dali-toolkit/internal/text/rendering/text-typesetter.h index 62d9d94..c1408cc 100644 --- a/dali-toolkit/internal/text/rendering/text-typesetter.h +++ b/dali-toolkit/internal/text/rendering/text-typesetter.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_TYPESETTER_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -22,6 +22,8 @@ #include #include #include +#include +#include namespace Dali { @@ -43,6 +45,33 @@ typedef IntrusivePtr TypesetterPtr; */ class Typesetter : public RefObject { +public: + + /** + * @brief Behaviours of how to render the text. + */ + enum RenderBehaviour + { + RENDER_TEXT_AND_STYLES, ///< Render both the text and its styles + RENDER_NO_TEXT, ///< Do not render the text itself + RENDER_NO_STYLES, ///< Do not render any styles + RENDER_MASK ///< Render an alpha mask (for color glyphs with no color animation, e.g. emoji) + }; + + /** + * @brief Styles of the text. + */ + enum Style + { + STYLE_NONE, ///< No style + STYLE_MASK, ///< Alpha mask + STYLE_SHADOW, ///< Hard shadow + STYLE_SOFT_SHADOW, ///< Soft shadow + STYLE_UNDERLINE, ///< Underline + STYLE_OUTLINE, ///< Outline + STYLE_BACKGROUND ///< Text background + }; + public: // Constructor. /** * @brief Creates a Typesetter instance. @@ -68,15 +97,16 @@ public: * Does the following operations: * - Finds the visible pages needed to be rendered. * - Elide glyphs if needed. - * - Retrieves the data buffers from the text model. - * - Creates the pixel data used to generate the final image with the given size. - * - Traverse the visible glyphs, retrieve their bitmaps and compose the final pixel data. + * - Creates image buffers for diffrent text styles with the given size. + * - Combines different image buffers to create the pixel data used to generate the final image * * @param[in] size The renderer size. + * @param[in] behaviour The behaviour of how to render the text (i.e. whether to render the text only or the styles only or both). + * @param[in] ignoreHorizontalAlignment Whether to ignore the horizontal alignment (i.e. always render as if HORIZONTAL_ALIGN_BEGIN). * * @return A pixel data with the text rendered. */ - PixelData Render( const Vector2& size ); + PixelData Render( const Vector2& size, RenderBehaviour behaviour = RENDER_TEXT_AND_STYLES, bool ignoreHorizontalAlignment = false ); private: /** @@ -92,6 +122,45 @@ private: // Declared private and left undefined to avoid copies. Typesetter& operator=( const Typesetter& handle ); + /** + * @brief Create the image buffer for the given range of the glyphs in the given style. + * + * Does the following operations: + * - Retrieves the data buffers from the text model. + * - Creates the pixel data used to generate the final image with the given size. + * - Traverse the visible glyphs, retrieve their bitmaps and compose the final pixel data. + * + * @param[in] bufferWidth The width of the image buffer. + * @param[in] bufferHeight The height of the image buffer. + * @param[in] style The style of the text. + * @param[in] ignoreHorizontalAlignment Whether to ignore the horizontal alignment, not ignored by default. + * @param[in] verticalOffset The vertical offset to be added to the glyph's position. + * @param[in] fromGlyphIndex The index of the first glyph within the text to be drawn + * @param[in] toGlyphIndex The index of the last glyph within the text to be drawn + * + * @return An image buffer with the text. + */ + Devel::PixelBuffer CreateImageBuffer( const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, int verticalOffset, TextAbstraction::GlyphIndex fromGlyphIndex, TextAbstraction::GlyphIndex toGlyphIndex ); + + /** + * @brief Combine the two image buffers together. + * + * The top layer buffer will blend over the bottom layer buffer: + * - If the pixel is not fully opaque from either buffer, it will be blended with + * the pixel from the other buffer and copied to the combined buffer. + * - If the pixels from both buffers are fully opaque, the pixels from the top layer + * buffer will be copied to the combined buffer. + * + * @param[in] topPixelBuffer The top layer buffer. + * @param[in] bottomPixelBuffer The bottom layer buffer. + * @param[in] bufferWidth The width of the image buffer. + * @param[in] bufferHeight The height of the image buffer. + * + * @return The combined image buffer with the text. + * + */ + Devel::PixelBuffer CombineImageBuffer( Devel::PixelBuffer topPixelBuffer, Devel::PixelBuffer bottomPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight ); + protected: /** @@ -102,6 +171,7 @@ protected: virtual ~Typesetter(); private: + ViewModel* mModel; }; diff --git a/dali-toolkit/internal/text/rendering/view-model.cpp b/dali-toolkit/internal/text/rendering/view-model.cpp index 5636506..3a0d319 100644 --- a/dali-toolkit/internal/text/rendering/view-model.cpp +++ b/dali-toolkit/internal/text/rendering/view-model.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -143,6 +143,41 @@ const Vector4& ViewModel::GetDefaultColor() const return mModel->GetDefaultColor(); } +const Vector2& ViewModel::GetShadowOffset() const +{ + return mModel->GetShadowOffset(); +} + +const Vector4& ViewModel::GetShadowColor() const +{ + return mModel->GetShadowColor(); +} + +const Vector4& ViewModel::GetUnderlineColor() const +{ + return mModel->GetUnderlineColor(); +} + +bool ViewModel::IsUnderlineEnabled() const +{ + return mModel->IsUnderlineEnabled(); +} + +float ViewModel::GetUnderlineHeight() const +{ + return mModel->GetUnderlineHeight(); +} + +Length ViewModel::GetNumberOfUnderlineRuns() const +{ + return mModel->GetNumberOfUnderlineRuns(); +} + +void ViewModel::GetUnderlineRuns( GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns ) const +{ + mModel->GetUnderlineRuns( underlineRuns, index, numberOfRuns ); +} + void ViewModel::ElideGlyphs() { mIsTextElided = false; diff --git a/dali-toolkit/internal/text/rendering/view-model.h b/dali-toolkit/internal/text/rendering/view-model.h index 8038820..df0e32c 100644 --- a/dali-toolkit/internal/text/rendering/view-model.h +++ b/dali-toolkit/internal/text/rendering/view-model.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_VIEW_MODEL_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -127,6 +127,41 @@ public: virtual const Vector4& GetDefaultColor() const; /** + * @copydoc ModelInterface::GetShadowOffset() + */ + virtual const Vector2& GetShadowOffset() const; + + /** + * @copydoc ModelInterface::GetShadowColor() + */ + virtual const Vector4& GetShadowColor() const; + + /** + * @copydoc ModelInterface::GetUnderlineColor() + */ + virtual const Vector4& GetUnderlineColor() const; + + /** + * @copydoc ModelInterface::IsUnderlineEnabled() + */ + virtual bool IsUnderlineEnabled() const; + + /** + * @copydoc ModelInterface::GetUnderlineHeight() + */ + virtual float GetUnderlineHeight() const; + + /** + * @copydoc ModelInterface::GetNumberOfUnderlineRuns() + */ + virtual Length GetNumberOfUnderlineRuns() const; + + /** + * @copydoc ModelInterface::GetUnderlineRuns() + */ + virtual void GetUnderlineRuns( GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns ) const; + + /** * @brief Does the text elide. * * It stores a copy of the visible glyphs and removes as many glyphs as needed diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index a992541..8b0ee5b 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -1743,14 +1743,14 @@ Vector3 Controller::GetNaturalSize() mImpl->UpdateModel( onlyOnceOperations ); // Layout the text for the new width. - mImpl->mOperationsPending = static_cast( mImpl->mOperationsPending | LAYOUT ); + mImpl->mOperationsPending = static_cast( mImpl->mOperationsPending | LAYOUT | REORDER ); // Store the actual control's size to restore later. const Size actualControlSize = mImpl->mModel->mVisualModel->mControlSize; DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ), static_cast( onlyOnceOperations | - LAYOUT ), + LAYOUT | REORDER ), naturalSize.GetVectorXY() ); // Do not do again the only once operations. @@ -3683,6 +3683,11 @@ void Controller::ResetScrollPosition() } } +void Controller::SetControlInterface( ControlInterface* controlInterface ) +{ + mImpl->mControlInterface = controlInterface; +} + // private : Private contructors & copy operator. Controller::Controller() diff --git a/dali-toolkit/internal/text/text-controller.h b/dali-toolkit/internal/text/text-controller.h index aeee224..5cb079c 100644 --- a/dali-toolkit/internal/text/text-controller.h +++ b/dali-toolkit/internal/text/text-controller.h @@ -999,6 +999,13 @@ public: // Default style & Input style */ const std::string& GetInputOutlineProperties() const; + /** + * @brief Set the control's interface. + * + * @param[in] controlInterface The control's interface. + */ + void SetControlInterface( ControlInterface* controlInterface ); + public: // Queries & retrieves. /** diff --git a/dali-toolkit/internal/text/text-model-interface.h b/dali-toolkit/internal/text/text-model-interface.h index e6b50c9..d84b46a 100644 --- a/dali-toolkit/internal/text/text-model-interface.h +++ b/dali-toolkit/internal/text/text-model-interface.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_MODEL_INTERFACE_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -144,6 +144,57 @@ public: * @return The default color. */ virtual const Vector4& GetDefaultColor() const = 0; + + /** + * @brief Retrieves the shadow offset, 0 indicates no shadow. + * + * @return The shadow offset. + */ + virtual const Vector2& GetShadowOffset() const = 0; + + /** + * @brief Retrieves the shadow color. + * + * @return The shadow color. + */ + virtual const Vector4& GetShadowColor() const = 0; + + /** + * @brief Retrieves the underline color. + * + * @return The underline color. + */ + virtual const Vector4& GetUnderlineColor() const = 0; + + /** + * @brief Returns whether underline is enabled or not. + * + * @return The underline state. + */ + virtual bool IsUnderlineEnabled() const = 0; + + /** + * @brief Retrieves the underline height override + * + * @return Returns the override height for an underline, 0 indicates that adaptor will determine the height + */ + virtual float GetUnderlineHeight() const = 0; + + /** + * @brief Retrieves the number of underline runs. + * + * @return The number of underline runs. + */ + virtual Length GetNumberOfUnderlineRuns() const = 0; + + /** + * @brief Retrieves the underline runs. + * + * @param[out] underlineRuns Pointer to a buffer where the underline runs are copied. + * @param[in] index Index of the first underline run to be copied. + * @param[in] numberOfRuns Number of underline runs to be copied. + */ + virtual void GetUnderlineRuns( GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns ) const = 0; }; } // namespace Text diff --git a/dali-toolkit/internal/text/text-model.cpp b/dali-toolkit/internal/text/text-model.cpp index f158dae..05cb405 100644 --- a/dali-toolkit/internal/text/text-model.cpp +++ b/dali-toolkit/internal/text/text-model.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -102,6 +102,41 @@ const Vector4& Model::GetDefaultColor() const return mVisualModel->mTextColor; } +const Vector2& Model::GetShadowOffset() const +{ + return mVisualModel->mShadowOffset; +} + +const Vector4& Model::GetShadowColor() const +{ + return mVisualModel->mShadowColor; +} + +const Vector4& Model::GetUnderlineColor() const +{ + return mVisualModel->GetUnderlineColor(); +} + +bool Model::IsUnderlineEnabled() const +{ + return mVisualModel->IsUnderlineEnabled(); +} + +float Model::GetUnderlineHeight() const +{ + return mVisualModel->GetUnderlineHeight(); +} + +Length Model::GetNumberOfUnderlineRuns() const +{ + return mVisualModel->GetNumberOfUnderlineRuns(); +} + +void Model::GetUnderlineRuns( GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns ) const +{ + mVisualModel->GetUnderlineRuns( underlineRuns, index, numberOfRuns ); +} + Model::Model() : mLogicalModel(), mVisualModel(), diff --git a/dali-toolkit/internal/text/text-model.h b/dali-toolkit/internal/text/text-model.h index 418989a..d8b08e9 100644 --- a/dali-toolkit/internal/text/text-model.h +++ b/dali-toolkit/internal/text/text-model.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_MODEL_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -129,6 +129,41 @@ public: */ virtual const Vector4& GetDefaultColor() const; + /** + * @copydoc ModelInterface::GetShadowOffset() + */ + virtual const Vector2& GetShadowOffset() const; + + /** + * @copydoc ModelInterface::GetShadowColor() + */ + virtual const Vector4& GetShadowColor() const; + + /** + * @copydoc ModelInterface::GetUnderlineColor() + */ + virtual const Vector4& GetUnderlineColor() const; + + /** + * @copydoc ModelInterface::IsUnderlineEnabled() + */ + virtual bool IsUnderlineEnabled() const; + + /** + * @copydoc ModelInterface::GetUnderlineHeight() + */ + virtual float GetUnderlineHeight() const; + + /** + * @copydoc ModelInterface::GetNumberOfUnderlineRuns() + */ + virtual Length GetNumberOfUnderlineRuns() const; + + /** + * @copydoc ModelInterface::GetUnderlineRuns() + */ + virtual void GetUnderlineRuns( GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns ) const; + private: // Private contructors & copy operator. /** diff --git a/dali-toolkit/internal/text/text-scroller.cpp b/dali-toolkit/internal/text/text-scroller.cpp index 590f919..49173e9 100644 --- a/dali-toolkit/internal/text/text-scroller.cpp +++ b/dali-toolkit/internal/text/text-scroller.cpp @@ -20,13 +20,6 @@ // EXTERNAL INCLUDES #include -#include -#include -#include -#include -#include -#include -#include #include // INTERNAL INCLUDES @@ -51,29 +44,24 @@ const char* VERTEX_SHADER_SCROLL = DALI_COMPOSE_SHADER( attribute mediump vec2 aPosition;\n varying highp vec2 vTexCoord;\n varying highp float vRatio;\n - uniform mediump mat4 uModelMatrix;\n - uniform mediump mat4 uViewMatrix;\n - uniform mediump mat4 uProjection;\n + uniform mediump mat4 uMvpMatrix;\n uniform mediump vec3 uSize;\n uniform mediump float uDelta;\n - uniform mediump vec2 uTextureSize; + uniform mediump vec2 uTextureSize;\n uniform mediump float uGap;\n - uniform mediump float uAlign;\n + uniform mediump float uHorizontalAlign;\n + uniform mediump float uVerticalAlign;\n \n void main()\n {\n {\n - highp vec4 vertexPosition = vec4(aPosition*uSize.xy, 0.0, 1.0);\n - vertexPosition = uViewMatrix * uModelMatrix * vertexPosition ;\n - vertexPosition.x = floor( vertexPosition.x ) + 0.5; - vertexPosition.y = floor( vertexPosition.y ) + 0.5; - float smallTextPadding = max(uSize.x - uTextureSize.x, 0. );\n + float smallTextPadding = max( uSize.x - uTextureSize.x, 0. );\n float gap = max( uGap, smallTextPadding );\n - float delta = floor ( uDelta ) + 0.5; - vTexCoord.x = ( delta + ( uAlign * ( uTextureSize.x - uSize.x ) ) + ( aPosition.x * uSize.x ) )/ ( uTextureSize.x + gap );\n - vTexCoord.y = ( 0.5 + floor( aPosition.y * uSize.y ) )/ ( uTextureSize.y ) ;\n + float delta = floor ( uDelta ) + 0.5;\n + vTexCoord.x = ( delta + uHorizontalAlign * ( uTextureSize.x - uSize.x ) + floor( aPosition.x * uSize.x ) + 0.5 - gap * 0.5 ) / (uTextureSize.x + gap) + 0.5;\n + vTexCoord.y = ( uVerticalAlign * ( uTextureSize.y - uSize.y ) + floor( aPosition.y * uSize.y ) + 0.5 ) / ( uTextureSize.y ) + 0.5;\n vRatio = uTextureSize.x / ( uTextureSize.x + gap );\n - gl_Position = uProjection * vertexPosition; + gl_Position = uMvpMatrix * vec4( floor( aPosition * uSize.xy ), 0.0, 1.0 );\n }\n }\n ); @@ -88,7 +76,7 @@ const char* FRAGMENT_SHADER = DALI_COMPOSE_SHADER( highp vec2 texCoord;\n texCoord.y = vTexCoord.y;\n texCoord.x = fract( vTexCoord.x ) / vRatio;\n - if ( texCoord.x > 1.0 )\n + if ( texCoord.x > 1.0 || texCoord.y > 1.0 )\n discard;\n \n gl_FragColor = texture2D( sTexture, texCoord );\n @@ -96,146 +84,46 @@ const char* FRAGMENT_SHADER = DALI_COMPOSE_SHADER( ); /** - * @brief How the text should be aligned when scrolling the text. + * @brief How the text should be aligned horizontally when scrolling the text. * - * 0.0f aligns the text to the left, 1.0f aligns the text to the right. - * The final alignment depends on three factors: + * -0.5f aligns the text to the left, 0.0f aligns the text to the center, 0.5f aligns the text to the right. + * The final alignment depends on two factors: * 1) The alignment value of the text label (Use Text::Layout::HorizontalAlignment enumerations). * 2) The text direction, i.e. whether it's LTR or RTL (0 = LTR, 1 = RTL). - * 3) Whether the text is greater than the size of the control ( 0 = Text width <= Control width, 1 = Text width > Control width ). */ -const float ALIGNMENT_TABLE[ Text::Layout::HORIZONTAL_ALIGN_COUNT ][ 2 ][ 2 ] = +const float HORIZONTAL_ALIGNMENT_TABLE[ Text::Layout::HORIZONTAL_ALIGN_COUNT ][ 2 ] = { // HORIZONTAL_ALIGN_BEGIN { - { // LTR - 0.0f, // Text width <= Control width - 0.0f // Text width > Control width - }, - { // RTL - 1.0f, // Text width <= Control width - 1.0f // Text width > Control width - } + -0.5f, // LTR + 0.5f // RTL }, // HORIZONTAL_ALIGN_CENTER { - { // LTR - 0.5f, // Text width <= Control width - 0.0f // Text width > Control width - }, - { // RTL - 0.5f, // Text width <= Control width - 1.0f // Text width > Control width - } + 0.0f, // LTR + 0.0f // RTL }, // HORIZONTAL_ALIGN_END { - { // LTR - 1.0f, // Text width <= Control width - 0.0f // Text width > Control width - }, - { // RTL - 0.0f, // Text width <= Control width - 1.0f // Text width > Control width - } + 0.5f, // LTR + -0.5f // RTL } }; /** - * @brief Create and set up a camera for the render task to use - * - * @param[in] sizeOfTarget size of the source camera to look at - * @param[out] offscreenCamera custom camera - */ -void CreateCameraActor( const Size& sizeOfTarget, CameraActor& offscreenCamera ) -{ - offscreenCamera = CameraActor::New(); - offscreenCamera.SetOrthographicProjection( sizeOfTarget ); - offscreenCamera.SetInvertYAxis( true ); -} - -/** - * @brief Create a render task - * - * @param[in] sourceActor actor to be used as source - * @param[in] cameraActor camera looking at source - * @param[in] offscreenTarget resulting image from render task - * @param[out] renderTask render task that has been setup - */ -void CreateRenderTask( Actor sourceActor, CameraActor cameraActor , FrameBufferImage offscreenTarget, RenderTask& renderTask ) -{ - Stage stage = Stage::GetCurrent(); - RenderTaskList taskList = stage.GetRenderTaskList(); - renderTask = taskList.CreateTask(); - renderTask.SetSourceActor( sourceActor ); - renderTask.SetExclusive( true ); - renderTask.SetInputEnabled( false ); - renderTask.SetClearEnabled( true ); - renderTask.SetCameraActor( cameraActor ); - renderTask.SetTargetFrameBuffer( offscreenTarget ); - renderTask.SetClearColor( Color::TRANSPARENT ); - renderTask.SetCullMode( false ); -} - -/** - * @brief Create quad geometry for the mesh - * - * @param[out] geometry quad geometry that can be used for a mesh - */ -void CreateGeometry( Geometry& geometry ) -{ - struct QuadVertex { Vector2 position; }; - - QuadVertex quadVertexData[4] = - { - { Vector2( 0.0f, 0.0f) }, - { Vector2( 1.0f, 0.0f) }, - { Vector2( 0.0f, 1.0f) }, - { Vector2( 1.0f, 1.0f) }, - }; - - const unsigned short indices[6] = - { - 3,1,0,0,2,3 - }; - - Property::Map quadVertexFormat; - quadVertexFormat["aPosition"] = Property::VECTOR2; - PropertyBuffer quadVertices = PropertyBuffer::New( quadVertexFormat ); - quadVertices.SetData(quadVertexData, 4 ); - - geometry = Geometry::New(); - geometry.AddVertexBuffer( quadVertices ); - geometry.SetIndexBuffer( indices, sizeof(indices)/sizeof(indices[0]) ); -} - - -/** - * @brief Create a renderer + * @brief How the text should be aligned vertically when scrolling the text. * - * @param[in] frameBufferImage texture to be used - * @param[out] renderer mesh renderer using the supplied texture + * -0.5f aligns the text to the top, 0.0f aligns the text to the center, 0.5f aligns the text to the bottom. + * The alignment depends on the alignment value of the text label (Use Text::Layout::VerticalAlignment enumerations). */ -void CreateRenderer( FrameBufferImage frameBufferImage, Dali::Renderer& renderer ) +const float VERTICAL_ALIGNMENT_TABLE[ Text::Layout::VERTICAL_ALIGN_COUNT ] = { - Shader shader = Shader::New( VERTEX_SHADER_SCROLL , FRAGMENT_SHADER, Shader::Hint::NONE ); - - Sampler sampler = Sampler::New(); - sampler.SetFilterMode(FilterMode::LINEAR, FilterMode::LINEAR ); - - TextureSet textureSet = TextureSet::New(); - TextureSetImage( textureSet, 0u, frameBufferImage ); - textureSet.SetSampler( 0u, sampler ); - - Geometry meshGeometry; - CreateGeometry( meshGeometry ); - - renderer = Renderer::New( meshGeometry, shader ); - renderer.SetProperty( Renderer::Property::BLEND_PRE_MULTIPLIED_ALPHA, true ); - renderer.SetTextures( textureSet ); -} + -0.5f, // VERTICAL_ALIGN_TOP + 0.0f, // VERTICAL_ALIGN_CENTER + 0.5f // VERTICAL_ALIGN_BOTTOM +}; } // namespace @@ -311,7 +199,6 @@ void TextScroller::StopScrolling() case DevelTextLabel::AutoScrollStopMode::IMMEDIATE: { mScrollAnimation.Stop(); - CleanUp(); mScrollerInterface.ScrollingFinished(); break; } @@ -333,38 +220,28 @@ DevelTextLabel::AutoScrollStopMode::Type TextScroller::GetStopMode() const return mStopMode; } -Actor TextScroller::GetSourceCamera() const -{ - return mOffscreenCameraActor; -} - -Actor TextScroller::GetScrollingText() const -{ - return mScrollingTextActor; -} - -TextScroller::TextScroller( ScrollerInterface& scrollerInterface ) : mScrollerInterface( scrollerInterface ), - mScrollDeltaIndex( Property::INVALID_INDEX ), - mScrollSpeed( MINIMUM_SCROLL_SPEED ), - mLoopCount( 1 ), - mLoopDelay( 0.0f ), - mWrapGap( 0.0f ), - mStopMode( DevelTextLabel::AutoScrollStopMode::FINISH_LOOP ) +TextScroller::TextScroller( ScrollerInterface& scrollerInterface ) +: mScrollerInterface( scrollerInterface ), + mScrollDeltaIndex( Property::INVALID_INDEX ), + mScrollSpeed( MINIMUM_SCROLL_SPEED ), + mLoopCount( 1 ), + mLoopDelay( 0.0f ), + mWrapGap( 0.0f ), + mStopMode( DevelTextLabel::AutoScrollStopMode::FINISH_LOOP ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller Default Constructor\n" ); } TextScroller::~TextScroller() { - CleanUp(); } -void TextScroller::SetParameters( Actor sourceActor, const Size& controlSize, const Size& offScreenSize, CharacterDirection direction, float alignmentOffset, Layout::HorizontalAlignment horizontalAlignment ) +void TextScroller::SetParameters( Actor scrollingTextActor, Renderer renderer, TextureSet textureSet, const Size& controlSize, const Size& textNaturalSize, CharacterDirection direction, Layout::HorizontalAlignment horizontalAlignment, Layout::VerticalAlignment verticalAlignment ) { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters controlSize[%f,%f] offscreenSize[%f,%f] direction[%d] alignmentOffset[%f]\n", - controlSize.x, controlSize.y, offScreenSize.x, offScreenSize.y, direction, alignmentOffset ); + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters controlSize[%f,%f] offscreenSize[%f,%f] direction[%d]\n", + controlSize.x, controlSize.y, textNaturalSize.x, textNaturalSize.y, direction ); - CleanUp(); // If already scrolling then restart with new parameters + mRenderer = renderer; float animationProgress = 0.0f; int remainedLoop = mLoopCount; @@ -381,114 +258,69 @@ void TextScroller::SetParameters( Actor sourceActor, const Size& controlSize, co } } mScrollAnimation.Clear(); - } - - FrameBufferImage offscreenRenderTargetForText = FrameBufferImage::New( offScreenSize.width, offScreenSize.height, Pixel::RGBA8888 ); - Renderer renderer; - - CreateCameraActor( offScreenSize, mOffscreenCameraActor ); - CreateRenderer( offscreenRenderTargetForText, renderer ); - CreateRenderTask( sourceActor, mOffscreenCameraActor, offscreenRenderTargetForText, mRenderTask ); - - float xPosition = 0.0f; - switch( horizontalAlignment ) - { - case Layout::HORIZONTAL_ALIGN_BEGIN: - { - // Reposition camera to match alignment of target, RTL text has direction=true - if ( direction ) - { - xPosition = alignmentOffset + offScreenSize.width * 0.5f; - } - else - { - xPosition = offScreenSize.width * 0.5f; - } - break; - } - case Layout::HORIZONTAL_ALIGN_CENTER: - { - xPosition = controlSize.width * 0.5f; - break; - } - - case Layout::HORIZONTAL_ALIGN_END: - { - // Reposition camera to match alignment of target, RTL text has direction=true - if ( direction ) - { - xPosition = offScreenSize.width * 0.5f; - } - else - { - xPosition = alignmentOffset + offScreenSize.width * 0.5f; - } - break; - } + // Reset to the original shader and texture before scrolling + mRenderer.SetShader(mShader); + mRenderer.SetTextures( mTextureSet ); } - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters xPosition[%f]\n", xPosition ); + mShader = mRenderer.GetShader(); + mTextureSet = mRenderer.GetTextures(); - mOffscreenCameraActor.SetX( xPosition ); - mOffscreenCameraActor.SetY( offScreenSize.height * 0.5f ); + // Set the shader and texture for scrolling + Shader shader = Shader::New( VERTEX_SHADER_SCROLL, FRAGMENT_SHADER, Shader::Hint::NONE ); + mRenderer.SetShader( shader ); + mRenderer.SetTextures( textureSet ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters mWrapGap[%f]\n", mWrapGap ); - const float align = ALIGNMENT_TABLE[ horizontalAlignment ][ direction ][ offScreenSize.width > controlSize.width ]; - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters align[%f]\n", align ); + const float horizontalAlign = HORIZONTAL_ALIGNMENT_TABLE[ horizontalAlignment ][ direction ]; + const float verticalAlign = VERTICAL_ALIGNMENT_TABLE[ verticalAlignment ]; + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::SetParameters horizontalAlign[%f], verticalAlign[%f]\n", horizontalAlign, verticalAlign ); - mScrollingTextActor = Actor::New(); - mScrollingTextActor.AddRenderer( renderer ); - mScrollingTextActor.RegisterProperty( "uTextureSize", offScreenSize ); - mScrollingTextActor.RegisterProperty( "uAlign", align ); - mScrollingTextActor.RegisterProperty( "uGap", mWrapGap ); - mScrollingTextActor.SetSize( controlSize.width, std::min( offScreenSize.height, controlSize.height ) ); - mScrollDeltaIndex = mScrollingTextActor.RegisterProperty( "uDelta", 0.0f ); + scrollingTextActor.RegisterProperty( "uTextureSize", textNaturalSize ); + scrollingTextActor.RegisterProperty( "uHorizontalAlign", horizontalAlign ); + scrollingTextActor.RegisterProperty( "uVerticalAlign", verticalAlign ); + scrollingTextActor.RegisterProperty( "uGap", mWrapGap ); + mScrollDeltaIndex = scrollingTextActor.RegisterProperty( "uDelta", 0.0f ); - float scrollAmount = std::max( offScreenSize.width + mWrapGap, controlSize.width ); + float scrollAmount = std::max( textNaturalSize.width + mWrapGap, controlSize.width ); float scrollDuration = scrollAmount / mScrollSpeed; if ( direction ) { - scrollAmount = -scrollAmount; // reverse direction of scrollung + scrollAmount = -scrollAmount; // reverse direction of scrolling } - StartScrolling( scrollAmount, scrollDuration, remainedLoop ); + StartScrolling( scrollingTextActor, scrollAmount, scrollDuration, remainedLoop ); mScrollAnimation.SetCurrentProgress(animationProgress); } void TextScroller::AutoScrollAnimationFinished( Dali::Animation& animation ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::AutoScrollAnimationFinished\n" ); - CleanUp(); mScrollerInterface.ScrollingFinished(); + + // Revert to the original shader and texture after scrolling + mRenderer.SetShader(mShader); + if ( mTextureSet ) + { + mRenderer.SetTextures( mTextureSet ); + } } -void TextScroller::StartScrolling( float scrollAmount, float scrollDuration, int loopCount ) +void TextScroller::StartScrolling( Actor scrollingTextActor, float scrollAmount, float scrollDuration, int loopCount ) { DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextScroller::StartScrolling scrollAmount[%f] scrollDuration[%f], loop[%d] speed[%d]\n", scrollAmount, scrollDuration, loopCount, mScrollSpeed ); mScrollAnimation = Animation::New( scrollDuration ); - mScrollAnimation.AnimateTo( Property( mScrollingTextActor, mScrollDeltaIndex ), scrollAmount, TimePeriod( mLoopDelay, scrollDuration ) ); + mScrollAnimation.AnimateTo( Property( scrollingTextActor, mScrollDeltaIndex ), scrollAmount, TimePeriod( mLoopDelay, scrollDuration ) ); mScrollAnimation.SetEndAction( Animation::Discard ); mScrollAnimation.SetLoopCount( loopCount ); mScrollAnimation.FinishedSignal().Connect( this, &TextScroller::AutoScrollAnimationFinished ); mScrollAnimation.Play(); } -void TextScroller::CleanUp() -{ - if ( Stage::IsInstalled() ) - { - Stage stage = Stage::GetCurrent(); - RenderTaskList taskList = stage.GetRenderTaskList(); - UnparentAndReset( mScrollingTextActor ); - UnparentAndReset( mOffscreenCameraActor ); - taskList.RemoveTask( mRenderTask ); - } -} - } // namespace Text } // namespace Toolkit diff --git a/dali-toolkit/internal/text/text-scroller.h b/dali-toolkit/internal/text/text-scroller.h index 2d611bb..b8dc10f 100644 --- a/dali-toolkit/internal/text/text-scroller.h +++ b/dali-toolkit/internal/text/text-scroller.h @@ -22,6 +22,7 @@ #include #include #include +#include // INTERNAL INCLUDES #include @@ -60,14 +61,16 @@ public: /** * @brief Set parameters relating to source required for scrolling * - * @param[in] sourceActor source actor to be scrolled + * @param[in] scrollingTextActor actor containing the text to be scrolled + * @param[in] renderer renderer to render the text + * @param[in] textureSet texture of the text to be scrolled * @param[in] controlSize size of the control to scroll within - * @param[in] offScreenSize size of the sourceActor + * @param[in] textNaturalSize natural size of the text * @param[in] direction text direction true for right to left text - * @param[in] alignmentOffset alignment of source text - * + * @param[in] horizontalAlignment horizontal alignment of the text + * @param[in] verticalAlignment vertical alignment of the text */ - void SetParameters( Actor sourceActor, const Size& controlSize, const Size& offScreenSize, CharacterDirection direction, float alignmentOffset, Layout::HorizontalAlignment horizontalAlignment ); + void SetParameters( Actor scrollingTextActor, Dali::Renderer renderer, TextureSet textureSet, const Size& controlSize, const Size& offScreenSize, CharacterDirection direction, Layout::HorizontalAlignment horizontalAlignment, Layout::VerticalAlignment verticalAlignment ); /** * @brief Set the gap distance to elapse before the text wraps around @@ -134,18 +137,6 @@ public: */ DevelTextLabel::AutoScrollStopMode::Type GetStopMode() const; - /** - * @brief Get the camera used to look at source, should be added to the parent of target actor. - * @return camera Actor - */ - Actor GetSourceCamera() const; - - /** - * @brief Get the resulting scrolling text actor, add to target actor which will show scrolling text - * @return mesh Actor - */ - Actor GetScrollingText() const; - private: // Implementation /** @@ -172,25 +163,21 @@ private: // Implementation /** * @brief variables required to set up scrolling animation + * @param[in] scrollingTextActor actor that shows scrolling text * @param[in] scrollAmount distance to animate text for the given duration * @param[in] scrollDuration duration of aninmation * @param[in] loopCount number of times to loop the scrolling text */ - void StartScrolling( float scrollAmount, float scrollDuration, int loopCount ); - - /** - * @brief When scrolling ended, the actors are cleaned up so no longer staged. - */ - void CleanUp(); + void StartScrolling( Actor scrollingTextActor, float scrollAmount, float scrollDuration, int loopCount ); private: - RenderTask mRenderTask; // Renders full text to a FrameBuffer which is then scrolled. - CameraActor mOffscreenCameraActor; // Camera used by render task - Actor mScrollingTextActor; // Actor used to show scrolling text ScrollerInterface& mScrollerInterface; // Interface implemented by control that requires scrolling Property::Index mScrollDeltaIndex; // Property used by shader to represent distance to scroll Animation mScrollAnimation; // Animation used to update the mScrollDeltaIndex + Dali::Renderer mRenderer; // Renderer used to render the text + Shader mShader; // Shader originally used by the renderer while not scrolling + TextureSet mTextureSet; // Texture originally used by the renderer while not scrolling int mScrollSpeed; ///< Speed which text should automatically scroll at int mLoopCount; ///< Number of time the text should scroll diff --git a/dali-toolkit/internal/text/text-view.cpp b/dali-toolkit/internal/text/text-view.cpp index 88c9b42..9e8d0be 100644 --- a/dali-toolkit/internal/text/text-view.cpp +++ b/dali-toolkit/internal/text/text-view.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -384,7 +384,7 @@ Length View::GetNumberOfUnderlineRuns() const { if( mImpl->mVisualModel ) { - return mImpl->mVisualModel->mUnderlineRuns.Count(); + return mImpl->mVisualModel->GetNumberOfUnderlineRuns(); } return 0u; diff --git a/dali-toolkit/internal/text/visual-model-impl.cpp b/dali-toolkit/internal/text/visual-model-impl.cpp index 92bc48d..1b114c4 100644 --- a/dali-toolkit/internal/text/visual-model-impl.cpp +++ b/dali-toolkit/internal/text/visual-model-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -390,6 +390,11 @@ float VisualModel::GetUnderlineHeight() const return mUnderlineHeight; } +Length VisualModel::GetNumberOfUnderlineRuns() const +{ + return mUnderlineRuns.Count(); +} + void VisualModel::ClearCaches() { mCachedLineIndex = 0u; diff --git a/dali-toolkit/internal/text/visual-model-impl.h b/dali-toolkit/internal/text/visual-model-impl.h index b024307..6356f32 100644 --- a/dali-toolkit/internal/text/visual-model-impl.h +++ b/dali-toolkit/internal/text/visual-model-impl.h @@ -2,7 +2,7 @@ #define __DALI_TOOLKIT_TEXT_VISUAL_MODEL_IMPL_H__ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -280,6 +280,13 @@ public: */ float GetUnderlineHeight() const; + /** + * @brief Retrieves the number of underline runs. + * + * @return The number of underline runs. + */ + Length GetNumberOfUnderlineRuns() const; + protected: /** diff --git a/dali-toolkit/internal/visuals/text/text-visual.cpp b/dali-toolkit/internal/visuals/text/text-visual.cpp index 2b5215a..8870fc0 100644 --- a/dali-toolkit/internal/visuals/text/text-visual.cpp +++ b/dali-toolkit/internal/visuals/text/text-visual.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -18,6 +18,9 @@ // CLASS HEADER #include +// EXTERNAL INCLUDES +#include + // INTERNAL HEADER #include #include @@ -27,6 +30,7 @@ #include #include #include +#include namespace Dali { @@ -49,6 +53,8 @@ const char * const HORIZONTAL_ALIGNMENT_PROPERTY( "horizontalAlignment" ); const char * const VERTICAL_ALIGNMENT_PROPERTY( "verticalAlignment" ); const char * const TEXT_COLOR_PROPERTY( "textColor" ); const char * const ENABLE_MARKUP_PROPERTY( "enableMarkup" ); +const char * const SHADOW_PROPERTY( "shadow" ); +const char * const UNDERLINE_PROPERTY( "underline" ); const Scripting::StringEnum HORIZONTAL_ALIGNMENT_STRING_TABLE[] = { @@ -90,14 +96,14 @@ const char* VERTEX_SHADER = DALI_COMPOSE_SHADER( attribute mediump vec2 aPosition;\n uniform mediump mat4 uMvpMatrix;\n uniform mediump vec3 uSize;\n - uniform mediump vec4 pixelArea; + uniform mediump vec4 pixelArea;\n uniform mediump mat4 uModelMatrix;\n uniform mediump mat4 uViewMatrix;\n uniform mediump mat4 uProjection;\n varying mediump vec2 vTexCoord;\n - \n + //Visual size and offset uniform mediump vec2 offset;\n uniform mediump vec2 size;\n @@ -124,18 +130,34 @@ const char* VERTEX_SHADER = DALI_COMPOSE_SHADER( ); const char* FRAGMENT_SHADER_ATLAS_CLAMP = DALI_COMPOSE_SHADER( - varying mediump vec2 vTexCoord;\n - uniform sampler2D sTexture;\n - uniform mediump vec4 uAtlasRect;\n - uniform lowp vec4 uColor;\n - uniform lowp vec3 mixColor;\n - uniform lowp float opacity;\n - \n - void main()\n - {\n - mediump vec2 texCoord = clamp( mix( uAtlasRect.xy, uAtlasRect.zw, vTexCoord ), uAtlasRect.xy, uAtlasRect.zw );\n - gl_FragColor = texture2D( sTexture, texCoord ) * uColor * vec4( mixColor, opacity );\n - }\n + varying mediump vec2 vTexCoord;\n + uniform sampler2D sTexture;\n + uniform sampler2D sStyle;\n + uniform sampler2D sMask;\n + uniform lowp float uHasMultipleTextColors;\n + uniform lowp vec4 uTextColorAnimatable;\n + uniform mediump vec4 uAtlasRect;\n + uniform lowp vec4 uColor;\n + uniform lowp vec3 mixColor;\n + uniform lowp float opacity;\n + \n + void main()\n + {\n + mediump vec2 texCoord = clamp( mix( uAtlasRect.xy, uAtlasRect.zw, vTexCoord ), uAtlasRect.xy, uAtlasRect.zw );\n + mediump vec4 textTexture = texture2D( sTexture, texCoord );\n + mediump vec4 styleTexture = texture2D( sStyle, texCoord );\n + mediump vec4 maskTexture = texture2D( sMask, texCoord );\n + + // Set the color of non-transparent pixel in text to what it is animated to. + // Markup text with multiple text colors are not animated (but can be supported later on if required). + // Emoji color are not animated. + mediump vec4 textColor = textTexture * textTexture.a;\n + mediump float vstep = step( 0.0001, textColor.a );\n + textColor.rgb = mix( textColor.rgb, uTextColorAnimatable.rgb, vstep * maskTexture.a * ( 1.0 - uHasMultipleTextColors ) );\n + + // Draw the text as overlay above the style + gl_FragColor = ( textColor + styleTexture * ( 1.0 - textTexture.a ) ) * uColor * vec4( mixColor, opacity );\n + }\n ); /** @@ -188,6 +210,14 @@ Dali::Property::Index StringKeyToIndexKey( const std::string& stringKey ) { result = Toolkit::TextVisual::Property::ENABLE_MARKUP; } + else if( stringKey == SHADOW_PROPERTY ) + { + result = Toolkit::TextVisual::Property::SHADOW; + } + else if( stringKey == UNDERLINE_PROPERTY ) + { + result = Toolkit::TextVisual::Property::UNDERLINE; + } return result; } @@ -259,6 +289,12 @@ void TextVisual::DoCreatePropertyMap( Property::Map& map ) const map.Insert( Toolkit::TextVisual::Property::TEXT_COLOR, mController->GetDefaultColor() ); map.Insert( Toolkit::TextVisual::Property::ENABLE_MARKUP, mController->IsMarkupProcessorEnabled() ); + + GetShadowProperties( mController, value, Text::EffectStyle::DEFAULT ); + map.Insert( Toolkit::TextVisual::Property::SHADOW, value ); + + GetUnderlineProperties( mController, value, Text::EffectStyle::DEFAULT ); + map.Insert( Toolkit::TextVisual::Property::UNDERLINE, value ); } void TextVisual::DoCreateInstancePropertyMap( Property::Map& map ) const @@ -274,7 +310,9 @@ void TextVisual::DoCreateInstancePropertyMap( Property::Map& map ) const TextVisual::TextVisual( VisualFactoryCache& factoryCache ) : Visual::Base( factoryCache ), mController( Text::Controller::New() ), - mTypesetter( Text::Typesetter::New( mController->GetTextModel() ) ) + mTypesetter( Text::Typesetter::New( mController->GetTextModel() ) ), + mAnimatableTextColorPropertyIndex( Property::INVALID_INDEX ), + mRendererUpdateNeeded( false ) { } @@ -326,7 +364,24 @@ void TextVisual::DoSetOnStage( Actor& actor ) mImpl->mRenderer = Renderer::New( geometry, shader ); mImpl->mRenderer.SetProperty( Dali::Renderer::Property::DEPTH_INDEX, Toolkit::DepthIndex::CONTENT ); - UpdateRenderer( true ); // Renderer needs textures and to be added to control + const Vector4& defaultColor = mController->GetTextModel()->GetDefaultColor(); + Dali::Property::Index shaderTextColorIndex = mImpl->mRenderer.RegisterProperty( "uTextColorAnimatable", defaultColor ); + + if ( mAnimatableTextColorPropertyIndex != Property::INVALID_INDEX ) + { + // Create constraint for the animatable text's color Property with uTextColorAnimatable in the renderer. + if( shaderTextColorIndex != Property::INVALID_INDEX ) + { + Constraint constraint = Constraint::New( mImpl->mRenderer, shaderTextColorIndex, EqualToConstraint() ); + constraint.AddSource( Source( actor, mAnimatableTextColorPropertyIndex ) ); + constraint.Apply(); + } + } + + // Renderer needs textures and to be added to control + mRendererUpdateNeeded = true; + + UpdateRenderer(); } void TextVisual::DoSetOffStage( Actor& actor ) @@ -348,7 +403,7 @@ void TextVisual::DoSetOffStage( Actor& actor ) void TextVisual::OnSetTransform() { - UpdateRenderer( false ); + UpdateRenderer(); } void TextVisual::DoSetProperty( Dali::Property::Index index, const Dali::Property::Value& propertyValue ) @@ -423,10 +478,20 @@ void TextVisual::DoSetProperty( Dali::Property::Index index, const Dali::Propert } break; } + case Toolkit::TextVisual::Property::SHADOW: + { + SetShadowProperties( mController, propertyValue, Text::EffectStyle::DEFAULT ); + break; + } + case Toolkit::TextVisual::Property::UNDERLINE: + { + SetUnderlineProperties( mController, propertyValue, Text::EffectStyle::DEFAULT ); + break; + } } } -void TextVisual::UpdateRenderer( bool initializeRendererAndTexture ) +void TextVisual::UpdateRenderer() { Actor control = mControl.GetHandle(); if( !control ) @@ -463,8 +528,11 @@ void TextVisual::UpdateRenderer( bool initializeRendererAndTexture ) const Text::Controller::UpdateTextType updateTextType = mController->Relayout( relayoutSize ); - if( Text::Controller::NONE_UPDATED != ( Text::Controller::MODEL_UPDATED & updateTextType ) || initializeRendererAndTexture ) + if( Text::Controller::NONE_UPDATED != ( Text::Controller::MODEL_UPDATED & updateTextType ) + || mRendererUpdateNeeded ) { + mRendererUpdateNeeded = false; + // Removes the texture set. RemoveTextureSet(); @@ -477,11 +545,10 @@ void TextVisual::UpdateRenderer( bool initializeRendererAndTexture ) if( ( relayoutSize.width > Math::MACHINE_EPSILON_1000 ) && ( relayoutSize.height > Math::MACHINE_EPSILON_1000 ) ) { - PixelData data = mTypesetter->Render( relayoutSize ); - Vector4 atlasRect = FULL_TEXTURE_RECT; - // Texture set not retrieved from Atlas Manager whilst pixel offset visible. + // Create a texture for the text without any styles + PixelData data = mTypesetter->Render( relayoutSize, Text::Typesetter::RENDER_NO_STYLES ); // It may happen the image atlas can't handle a pixel data it exceeds the maximum size. // In that case, create a texture. TODO: should tile the text. @@ -496,20 +563,51 @@ void TextVisual::UpdateRenderer( bool initializeRendererAndTexture ) TextureSet textureSet = TextureSet::New(); textureSet.SetTexture( 0u, texture ); - mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; + // Create a texture for all the text styles (without the text itself) + PixelData styleData = mTypesetter->Render( relayoutSize, Text::Typesetter::RENDER_NO_TEXT ); - mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, atlasRect ); + Texture styleTexture = Texture::New( Dali::TextureType::TEXTURE_2D, + styleData.GetPixelFormat(), + styleData.GetWidth(), + styleData.GetHeight() ); - //Register transform properties - mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT ); + styleTexture.Upload( styleData ); + + textureSet.SetTexture( 1u, styleTexture ); + + // Create a texture as a mask to avoid color glyphs (e.g. emojis) to be affected by text color animation + PixelData maskData = mTypesetter->Render( relayoutSize, Text::Typesetter::RENDER_MASK ); + + Texture maskTexture = Texture::New( Dali::TextureType::TEXTURE_2D, + styleData.GetPixelFormat(), + styleData.GetWidth(), + styleData.GetHeight() ); + + maskTexture.Upload( maskData ); + + textureSet.SetTexture( 2u, maskTexture ); // Filter mode needs to be set to nearest to avoid blurry text. Sampler sampler = Sampler::New(); sampler.SetFilterMode( FilterMode::NEAREST, FilterMode::NEAREST ); textureSet.SetSampler( 0u, sampler ); + textureSet.SetSampler( 1u, sampler ); + textureSet.SetSampler( 2u, sampler ); mImpl->mRenderer.SetTextures( textureSet ); + mImpl->mFlags &= ~Impl::IS_ATLASING_APPLIED; + + mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, atlasRect ); + + // Check whether it is a markup text with multiple text colors + const Vector4* const colorsBuffer = mController->GetTextModel()->GetColors(); + bool hasMultipleTextColors = ( NULL != colorsBuffer ); + mImpl->mRenderer.RegisterProperty( "uHasMultipleTextColors", static_cast( hasMultipleTextColors ) ); + + //Register transform properties + mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT ); + control.AddRenderer( mImpl->mRenderer ); // Text rendered and ready to display diff --git a/dali-toolkit/internal/visuals/text/text-visual.h b/dali-toolkit/internal/visuals/text/text-visual.h index b11f548..2ed7bee 100644 --- a/dali-toolkit/internal/visuals/text/text-visual.h +++ b/dali-toolkit/internal/visuals/text/text-visual.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_INTERNAL_TEXT_VISUAL_H /* - * Copyright (c) 2016 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -18,6 +18,9 @@ * */ +// EXTERNAL INCLUDES +#include + // INTERNAL INCLUDES #include #include @@ -81,6 +84,35 @@ public: */ static void ConvertStringKeysToIndexKeys( Property::Map& propertyMap ); + /** + * @brief Retrieve the text's controller. + * @param[in] visual The text visual. + * @return The text controller + */ + static Text::ControllerPtr GetController( Toolkit::Visual::Base visual ) + { + return GetVisualObject( visual ).mController; + }; + + /** + * @brief Set the index of the animatable text color property. + * @param[in] visual The text visual. + * @param[in] animatablePropertyIndex The index of the animatable property + */ + static void SetAnimatableTextColorProperty( Toolkit::Visual::Base visual, Property::Index animatablePropertyIndex ) + { + GetVisualObject( visual ).mAnimatableTextColorPropertyIndex = animatablePropertyIndex; + }; + + /** + * @brief Set the flag to trigger the textures to be initialized and renderer to be added to the control. + * @param[in] visual The text visual. + */ + static void EnableRendererUpdate( Toolkit::Visual::Base visual ) + { + GetVisualObject( visual ).mRendererUpdateNeeded = true; + }; + public: // from Visual::Base /** @@ -151,19 +183,30 @@ private: /** * @brief Updates the text's renderer. - * @param[in] initializeRendererAndTexture Set flag to true to initialize textures and add renderer to control. */ - void UpdateRenderer( bool initializeRendererAndTexture ); + void UpdateRenderer(); /** * @brief Removes the texture set from the renderer. */ void RemoveTextureSet(); + /** + * @brief Retrieve the text's controller. + * @param[in] visual The text visual. + * @return The text controller + */ + static TextVisual& GetVisualObject( Toolkit::Visual::Base visual ) + { + return static_cast( visual.GetBaseObject() ); + }; + private: - Text::ControllerPtr mController; ///< The text's controller. - Text::TypesetterPtr mTypesetter; ///< The text's typesetter. - WeakHandle mControl; ///< The control where the renderer is added. + Text::ControllerPtr mController; ///< The text's controller. + Text::TypesetterPtr mTypesetter; ///< The text's typesetter. + WeakHandle mControl; ///< The control where the renderer is added. + Property::Index mAnimatableTextColorPropertyIndex; ///< The index of animatable text color property registered by the control. + bool mRendererUpdateNeeded:1; ///< The flag to indicate whether the renderer needs to be updated. }; } // namespace Internal diff --git a/dali-toolkit/public-api/controls/text-controls/text-label.h b/dali-toolkit/public-api/controls/text-controls/text-label.h index 7b331a6..df0abce 100644 --- a/dali-toolkit/public-api/controls/text-controls/text-label.h +++ b/dali-toolkit/public-api/controls/text-controls/text-label.h @@ -2,7 +2,7 @@ #define __DALI_TOOLKIT_TEXT_LABEL_H__ /* - * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * Copyright (c) 2017 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. @@ -90,6 +90,7 @@ public: enum { /** + * DEPRECATED_1_2.53 No longer be supported and will be ignored. * @brief The type of rendering e.g. bitmap-based. * @details name "renderingBackend", type INT, default RENDERING_SHARED_ATLAS. * @SINCE_1_0.0