2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.h>
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/animation/constraints.h>
25 #include <dali/public-api/rendering/geometry.h>
26 #include <dali/public-api/rendering/renderer.h>
29 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
30 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
31 #include <dali-toolkit/internal/text/glyph-run.h>
32 #include <dali-toolkit/internal/text/rendering/atlas/atlas-glyph-manager.h>
33 #include <dali-toolkit/internal/text/rendering/atlas/atlas-mesh-factory.h>
34 #include <dali-toolkit/internal/text/text-view.h>
37 using namespace Dali::Toolkit;
38 using namespace Dali::Toolkit::Text;
42 #if defined(DEBUG_ENABLED)
43 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_RENDERING");
46 const float ZERO(0.0f);
47 const float HALF(0.5f);
48 const float ONE(1.0f);
49 const float ONE_AND_A_HALF(1.5f);
50 const uint32_t DOUBLE_PIXEL_PADDING = 4u; //Padding will be added twice to Atlas
51 const uint16_t NO_OUTLINE = 0u;
54 struct AtlasRenderer::Impl
70 AtlasManager::Mesh2D mMesh;
74 * brief Struct used to generate the underline/striketthrough mesh.
75 * There is one Extent per line of text.
83 mUnderlinePosition(0.0f),
86 mUnderlineChunkId(0u),
87 mStrikethroughPosition(0.0f)
94 float mUnderlinePosition;
96 uint32_t mMeshRecordIndex;
97 uint32_t mUnderlineChunkId;
98 float mStrikethroughPosition;
105 mNeededBlockWidth(0),
106 mNeededBlockHeight(0)
111 uint32_t mNeededBlockWidth;
112 uint32_t mNeededBlockHeight;
124 Text::GlyphIndex mIndex;
127 struct TextCacheEntry
140 Text::GlyphIndex mIndex;
142 uint16_t mOutlineWidth;
150 mGlyphManager = AtlasGlyphManager::Get();
151 mFontClient = TextAbstraction::FontClient::Get();
153 mQuadVertexFormat["aPosition"] = Property::VECTOR2;
154 mQuadVertexFormat["aTexCoord"] = Property::VECTOR2;
155 mQuadVertexFormat["aColor"] = Property::VECTOR4;
158 bool IsGlyphUnderlined(GlyphIndex index,
159 const Vector<GlyphRun>& underlineRuns)
161 for(Vector<GlyphRun>::ConstIterator it = underlineRuns.Begin(),
162 endIt = underlineRuns.End();
166 const GlyphRun& run = *it;
168 if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs))
177 void CacheGlyph(const GlyphInfo& glyph, FontId lastFontId, const AtlasGlyphManager::GlyphStyle& style, AtlasManager::AtlasSlot& slot)
179 const Size& defaultTextAtlasSize = mFontClient.GetDefaultTextAtlasSize(); //Retrieve default size of text-atlas-block from font-client.
180 const Size& maximumTextAtlasSize = mFontClient.GetMaximumTextAtlasSize(); //Retrieve maximum size of text-atlas-block from font-client.
182 const bool glyphNotCached = !mGlyphManager.IsCached(glyph.fontId, glyph.index, style, slot); // Check FontGlyphRecord vector for entry with glyph index and fontId
184 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "AddGlyphs fontID[%u] glyphIndex[%u] [%s]\n", glyph.fontId, glyph.index, (glyphNotCached) ? "not cached" : "cached");
188 MaxBlockSize& blockSize = mBlockSizes[0u];
190 if(lastFontId != glyph.fontId)
193 // Looks through all stored block sizes until finds the one which mataches required glyph font it. Ensures new atlas block size will match existing for same font id.
194 // CalculateBlocksSize() above ensures a block size entry exists.
195 for(std::vector<MaxBlockSize>::const_iterator it = mBlockSizes.begin(),
196 endIt = mBlockSizes.end();
200 const MaxBlockSize& blockSizeEntry = *it;
201 if(blockSizeEntry.mFontId == glyph.fontId)
203 blockSize = mBlockSizes[index];
208 // Create a new image for the glyph
211 // Whether the glyph is an outline.
212 const bool isOutline = 0u != style.outline;
214 // Whether the current glyph is a color one.
215 const bool isColorGlyph = mFontClient.IsColorGlyph(glyph.fontId, glyph.index);
217 if(!isOutline || (isOutline && !isColorGlyph))
219 // Retrieve the emoji's bitmap.
220 TextAbstraction::FontClient::GlyphBufferData glyphBufferData;
221 glyphBufferData.width = isColorGlyph ? glyph.width : 0; // Desired width and height.
222 glyphBufferData.height = isColorGlyph ? glyph.height : 0;
224 mFontClient.CreateBitmap(glyph.fontId,
226 glyph.isItalicRequired,
227 glyph.isBoldRequired,
231 // Create the pixel data.
232 bitmap = PixelData::New(glyphBufferData.buffer,
233 glyphBufferData.width * glyphBufferData.height * GetBytesPerPixel(glyphBufferData.format),
234 glyphBufferData.width,
235 glyphBufferData.height,
236 glyphBufferData.format,
237 PixelData::DELETE_ARRAY);
241 // Ensure that the next image will fit into the current block size
242 if(bitmap.GetWidth() > blockSize.mNeededBlockWidth)
244 blockSize.mNeededBlockWidth = bitmap.GetWidth();
247 if(bitmap.GetHeight() > blockSize.mNeededBlockHeight)
249 blockSize.mNeededBlockHeight = bitmap.GetHeight();
252 // If CheckAtlas in AtlasManager::Add can't fit the bitmap in the current atlas it will create a new atlas
254 // Setting the block size and size of new atlas does not mean a new one will be created. An existing atlas may still surffice.
255 uint32_t default_width = defaultTextAtlasSize.width;
256 uint32_t default_height = defaultTextAtlasSize.height;
259 (blockSize.mNeededBlockWidth >= (default_width - (DOUBLE_PIXEL_PADDING + 1u)) ||
260 blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u))) &&
261 (default_width < maximumTextAtlasSize.width &&
262 default_height < maximumTextAtlasSize.height))
264 default_width <<= 1u;
265 default_height <<= 1u;
267 mGlyphManager.SetNewAtlasSize(default_width,
269 blockSize.mNeededBlockWidth,
270 blockSize.mNeededBlockHeight);
272 // Locate a new slot for our glyph
273 mGlyphManager.Add(glyph, style, bitmap, slot); // slot will be 0 is glyph not added
279 // We have 2+ copies of the same glyph
280 mGlyphManager.AdjustReferenceCount(glyph.fontId, glyph.index, style, 1); //increment
284 void GenerateMesh(const GlyphInfo& glyph,
285 const Vector2& position,
286 const Vector4& color,
288 AtlasManager::AtlasSlot& slot,
289 bool decorationlineGlyph,
290 float currentUnderlinePosition,
291 float currentlineThickness,
292 std::vector<MeshRecord>& meshContainer,
293 Vector<TextCacheEntry>& newTextCache,
294 Vector<Extent>& extents,
295 uint32_t underlineChunkId,
298 // Generate mesh data for this quad, plugging in our supplied position
299 AtlasManager::Mesh2D newMesh;
300 mGlyphManager.GenerateMeshData(slot.mImageId, position, newMesh);
304 TextCacheEntry textCacheEntry;
305 textCacheEntry.mFontId = glyph.fontId;
306 textCacheEntry.mImageId = slot.mImageId;
307 textCacheEntry.mIndex = glyph.index;
308 textCacheEntry.mOutlineWidth = outline;
309 textCacheEntry.isItalic = glyph.isItalicRequired;
310 textCacheEntry.isBold = glyph.isBoldRequired;
312 newTextCache.PushBack(textCacheEntry);
315 AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin();
317 for(unsigned int index = 0u, size = newMesh.mVertices.Count();
321 AtlasManager::Vertex2D& vertex = *(verticesBuffer + index);
323 // Set the color of the vertex.
324 vertex.mColor = color;
327 // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas)
328 StitchTextMesh(meshContainer,
331 position.y + glyph.yBearing,
333 currentUnderlinePosition,
334 currentlineThickness,
337 position.y + (glyph.height * HALF));
340 void CreateActors(const std::vector<MeshRecord>& meshContainer,
341 const Size& textSize,
342 const Vector4& color,
343 const Vector4& shadowColor,
344 const Vector2& shadowOffset,
346 Property::Index animatablePropertyIndex,
351 // Create a container actor to act as a common parent for text and shadow, to avoid color inheritence issues.
352 mActor = Actor::New();
353 mActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
354 mActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
355 mActor.SetProperty(Actor::Property::SIZE, textSize);
356 mActor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
359 for(std::vector<MeshRecord>::const_iterator it = meshContainer.begin(),
360 endIt = meshContainer.end();
364 const MeshRecord& meshRecord = *it;
366 Actor actor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_NORMAL);
368 // Whether the actor has renderers.
369 const bool hasRenderer = actor.GetRendererCount() > 0u;
371 // Create an effect if necessary
375 // Change the color of the vertices.
376 for(Vector<AtlasManager::Vertex2D>::Iterator vIt = meshRecord.mMesh.mVertices.Begin(),
377 vEndIt = meshRecord.mMesh.mVertices.End();
381 AtlasManager::Vertex2D& vertex = *vIt;
383 vertex.mColor = shadowColor;
386 Actor shadowActor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_DROP_SHADOW);
387 #if defined(DEBUG_ENABLED)
388 shadowActor.SetProperty(Dali::Actor::Property::NAME, "Text Shadow renderable actor");
390 // Offset shadow in x and y
391 shadowActor.RegisterProperty("uOffset", shadowOffset);
392 Dali::Renderer renderer(shadowActor.GetRendererAt(0));
393 int depthIndex = renderer.GetProperty<int>(Dali::Renderer::Property::DEPTH_INDEX);
394 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, depthIndex - 1);
395 mActor.Add(shadowActor);
405 void AddGlyphs(Text::ViewInterface& view,
407 Property::Index animatablePropertyIndex,
408 const Vector<Vector2>& positions,
409 const Vector<GlyphInfo>& glyphs,
410 const Vector4& defaultColor,
411 const Vector4* const colorsBuffer,
412 const ColorIndex* const colorIndicesBuffer,
416 AtlasManager::AtlasSlot slot;
420 AtlasManager::AtlasSlot slotOutline;
421 slotOutline.mImageId = 0u;
422 slotOutline.mAtlasId = 0u;
424 std::vector<MeshRecord> meshContainer;
425 std::vector<MeshRecord> meshContainerOutline;
426 Vector<Extent> extents;
427 Vector<Extent> strikethroughExtents;
430 const Vector2& textSize(view.GetLayoutSize());
431 const Vector2 halfTextSize(textSize * 0.5f);
432 const Vector2& shadowOffset(view.GetShadowOffset());
433 const Vector4& shadowColor(view.GetShadowColor());
434 const bool underlineEnabled = view.IsUnderlineEnabled();
435 const Vector4& underlineColor(view.GetUnderlineColor());
436 const float underlineHeight = view.GetUnderlineHeight();
437 const Text::Underline::Type underlineType = view.GetUnderlineType();
438 const float dashedUnderlineWidth = view.GetDashedUnderlineWidth();
439 const float dashedUnderlineGap = view.GetDashedUnderlineGap();
440 const uint16_t outlineWidth = view.GetOutlineWidth();
441 const Vector4& outlineColor(view.GetOutlineColor());
442 const bool isOutline = 0u != outlineWidth;
443 const GlyphInfo* hyphens = view.GetHyphens();
444 const Length* hyphenIndices = view.GetHyphenIndices();
445 const Length hyphensCount = view.GetHyphensCount();
446 const bool strikethroughEnabled = view.IsStrikethroughEnabled();
447 const Vector4& strikethroughColor(view.GetStrikethroughColor());
448 const float strikethroughHeight = view.GetStrikethroughHeight();
450 // Elided text info. Indices according to elided text.
451 const auto startIndexOfGlyphs = view.GetStartIndexOfElidedGlyphs();
452 const auto firstMiddleIndexOfElidedGlyphs = view.GetFirstMiddleIndexOfElidedGlyphs();
453 const auto secondMiddleIndexOfElidedGlyphs = view.GetSecondMiddleIndexOfElidedGlyphs();
455 const bool useDefaultColor = (NULL == colorsBuffer);
457 // Get the underline runs.
458 const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns();
459 Vector<GlyphRun> underlineRuns;
460 underlineRuns.Resize(numberOfUnderlineRuns);
461 view.GetUnderlineRuns(underlineRuns.Begin(),
463 numberOfUnderlineRuns);
465 bool thereAreUnderlinedGlyphs = false;
466 bool strikethroughGlyphsExist = false;
468 float currentUnderlinePosition = ZERO;
469 float currentUnderlineThickness = underlineHeight;
470 float currentStrikethroughThickness = strikethroughHeight;
471 FontId lastFontId = 0;
472 FontId lastUnderlinedFontId = 0;
473 Style style = STYLE_NORMAL;
475 if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
477 style = STYLE_DROP_SHADOW;
480 CalculateBlocksSize(glyphs);
482 // Avoid emptying mTextCache (& removing references) until after incremented references for the new text
483 Vector<TextCacheEntry> newTextCache;
484 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
485 const Vector2* const positionsBuffer = positions.Begin();
486 const Vector2 lineOffsetPosition(minLineOffset, 0.f);
487 uint32_t hyphenIndex = 0;
489 //For septated underlined chunks. (this is for Markup case)
490 uint32_t underlineChunkId = 0u; // give id for each chunk.
491 bool isPreUnderlined = false; // status of underlined for previous glyph.
493 //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text
496 while((hyphenIndex < hyphensCount) && (hyphenIndices[hyphenIndex] < startIndexOfGlyphs ||
497 (hyphenIndices[hyphenIndex] > firstMiddleIndexOfElidedGlyphs && hyphenIndices[hyphenIndex] < secondMiddleIndexOfElidedGlyphs)))
503 for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
506 bool addHyphen = ((hyphenIndex < hyphensCount) && hyphenIndices && ((i + startIndexOfGlyphs) == hyphenIndices[hyphenIndex]));
507 if(addHyphen && hyphens)
509 glyph = hyphens[hyphenIndex];
514 glyph = *(glyphsBuffer + i);
517 const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns);
518 thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || isGlyphUnderlined;
519 strikethroughGlyphsExist = strikethroughGlyphsExist || strikethroughEnabled;
521 // No operation for white space
522 if(glyph.width && glyph.height)
524 // Are we still using the same fontId as previous
525 if((isGlyphUnderlined || strikethroughGlyphsExist) && (glyph.fontId != lastUnderlinedFontId))
527 // We need to fetch fresh font underline metrics
528 FontMetrics fontMetrics;
529 mFontClient.GetFontMetrics(glyph.fontId, fontMetrics);
530 currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition));
531 const float descender = ceil(fabsf(fontMetrics.descender));
533 if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000)
535 currentUnderlineThickness = fontMetrics.underlineThickness;
537 // Ensure underline will be at least a pixel high
538 if(currentUnderlineThickness < ONE)
540 currentUnderlineThickness = ONE;
544 currentUnderlineThickness = ceil(currentUnderlineThickness);
548 if(fabsf(strikethroughHeight) < Math::MACHINE_EPSILON_1000)
550 // Ensure strikethrough will be at least a pixel high
551 if(currentStrikethroughThickness < ONE)
553 currentStrikethroughThickness = ONE;
557 currentStrikethroughThickness = ceil(currentStrikethroughThickness);
561 // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
562 if(currentUnderlinePosition > descender)
564 currentUnderlinePosition = descender;
567 if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000)
569 // Move offset down by one ( EFL behavior )
570 currentUnderlinePosition = ONE;
573 lastUnderlinedFontId = glyph.fontId;
577 AtlasGlyphManager::GlyphStyle style;
578 style.isItalic = glyph.isItalicRequired;
579 style.isBold = glyph.isBoldRequired;
581 // Retrieves and caches the glyph's bitmap.
582 CacheGlyph(glyph, lastFontId, style, slot);
584 // Retrieves and caches the outline glyph's bitmap.
587 style.outline = outlineWidth;
588 CacheGlyph(glyph, lastFontId, style, slotOutline);
591 // Move the origin (0,0) of the mesh to the center of the actor
592 Vector2 position = *(positionsBuffer + i);
596 GlyphInfo tempInfo = *(glyphsBuffer + i);
597 position.x = position.x + tempInfo.advance - tempInfo.xBearing + glyph.xBearing;
598 position.y += tempInfo.yBearing - glyph.yBearing;
601 position = Vector2(roundf(position.x), position.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues.
603 if(0u != slot.mImageId) // invalid slot id, glyph has failed to be added to atlas
605 Vector2 positionPlusOutlineOffset = position;
608 // Add an offset to the text.
609 const float outlineWidthOffset = static_cast<float>(outlineWidth);
610 positionPlusOutlineOffset += Vector2(outlineWidthOffset, outlineWidthOffset);
613 // Get the color of the character.
614 const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndicesBuffer + i);
615 const Vector4& color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + colorIndex - 1u);
618 positionPlusOutlineOffset,
623 currentUnderlinePosition,
624 currentUnderlineThickness,
631 if(strikethroughGlyphsExist)
634 positionPlusOutlineOffset,
638 strikethroughGlyphsExist,
640 currentStrikethroughThickness,
643 strikethroughExtents,
648 lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId.
651 if(isOutline && (0u != slotOutline.mImageId)) // invalid slot id, glyph has failed to be added to atlas
659 currentUnderlinePosition,
660 currentUnderlineThickness,
661 meshContainerOutline,
668 //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case)
669 // Examples: "Hello <u>World</u> Hello <u>World</u>", "<u>World</u> Hello <u>World</u>", "<u> World</u> Hello <u>World</u>"
670 if(isPreUnderlined && (isPreUnderlined != isGlyphUnderlined))
674 //Keep status of underlined for previous glyph to check consecutive indices
675 isPreUnderlined = isGlyphUnderlined;
684 // Now remove references for the old text
686 mTextCache.Swap(newTextCache);
688 if(thereAreUnderlinedGlyphs)
690 // Check to see if any of the text needs an underline
691 GenerateUnderlines(meshContainer, extents, underlineColor, underlineType, dashedUnderlineWidth, dashedUnderlineGap);
694 if(strikethroughGlyphsExist)
696 // Check to see if any of the text needs a strikethrough
697 GenerateStrikethrough(meshContainer, strikethroughExtents, strikethroughColor);
700 // For each MeshData object, create a mesh actor and add to the renderable actor
701 bool isShadowDrawn = false;
702 if(!meshContainerOutline.empty())
704 const bool drawShadow = STYLE_DROP_SHADOW == style;
705 CreateActors(meshContainerOutline,
711 animatablePropertyIndex,
714 isShadowDrawn = drawShadow;
717 // For each MeshData object, create a mesh actor and add to the renderable actor
718 if(!meshContainer.empty())
720 const bool drawShadow = !isShadowDrawn && (STYLE_DROP_SHADOW == style);
721 CreateActors(meshContainer,
727 animatablePropertyIndex,
731 #if defined(DEBUG_ENABLED)
732 Toolkit::AtlasGlyphManager::Metrics metrics = mGlyphManager.GetMetrics();
733 DALI_LOG_INFO(gLogFilter, Debug::General, "TextAtlasRenderer::GlyphManager::GlyphCount: %i, AtlasCount: %i, TextureMemoryUse: %iK\n", metrics.mGlyphCount, metrics.mAtlasMetrics.mAtlasCount, metrics.mAtlasMetrics.mTextureMemoryUsed / 1024);
735 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%s\n", metrics.mVerboseGlyphCounts.c_str());
737 for(uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i)
739 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);
746 for(Vector<TextCacheEntry>::Iterator oldTextIter = mTextCache.Begin(); oldTextIter != mTextCache.End(); ++oldTextIter)
748 AtlasGlyphManager::GlyphStyle style;
749 style.outline = oldTextIter->mOutlineWidth;
750 style.isItalic = oldTextIter->isItalic;
751 style.isBold = oldTextIter->isBold;
752 mGlyphManager.AdjustReferenceCount(oldTextIter->mFontId, oldTextIter->mIndex, style, -1 /*decrement*/);
754 mTextCache.Resize(0);
757 Actor CreateMeshActor(Actor textControl, Property::Index animatablePropertyIndex, const Vector4& defaultColor, const MeshRecord& meshRecord, const Vector2& actorSize, Style style)
759 VertexBuffer quadVertices = VertexBuffer::New(mQuadVertexFormat);
760 quadVertices.SetData(const_cast<AtlasManager::Vertex2D*>(&meshRecord.mMesh.mVertices[0]), meshRecord.mMesh.mVertices.Size());
762 Geometry quadGeometry = Geometry::New();
763 quadGeometry.AddVertexBuffer(quadVertices);
764 quadGeometry.SetIndexBuffer(&meshRecord.mMesh.mIndices[0], meshRecord.mMesh.mIndices.Size());
766 TextureSet textureSet(mGlyphManager.GetTextures(meshRecord.mAtlasId));
768 // Choose the shader to use.
769 const bool isColorShader = (STYLE_DROP_SHADOW != style) && (Pixel::BGRA8888 == mGlyphManager.GetPixelFormat(meshRecord.mAtlasId));
773 // The glyph is an emoji and is not a shadow.
776 mShaderRgba = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_RGBA_SHADER_FRAG);
778 shader = mShaderRgba;
782 // The glyph is text or a shadow.
785 mShaderL8 = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_L8_SHADER_FRAG);
790 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "defaultColor[%f, %f, %f, %f ]\n", defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.a);
792 Dali::Property::Index shaderTextColorIndex = shader.RegisterProperty("textColorAnimatable", defaultColor);
794 if(animatablePropertyIndex != Property::INVALID_INDEX)
796 // create constraint for the animatable text's color Property with textColorAnimatable in the shader.
797 if(shaderTextColorIndex)
799 Constraint constraint = Constraint::New<Vector4>(shader, shaderTextColorIndex, EqualToConstraint());
800 constraint.AddSource(Source(textControl, animatablePropertyIndex));
806 // If not animating the text colour then set to 1's so shader uses the current vertex color
807 shader.RegisterProperty("textColorAnimatable", Vector4(1.0, 1.0, 1.0, 1.0));
810 Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, shader);
811 renderer.SetTextures(textureSet);
812 renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
813 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT + mDepth);
815 Actor actor = Actor::New();
816 #if defined(DEBUG_ENABLED)
817 actor.SetProperty(Dali::Actor::Property::NAME, "Text renderable actor");
819 actor.AddRenderer(renderer);
820 // Keep all of the origins aligned
821 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
822 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
823 actor.SetProperty(Actor::Property::SIZE, actorSize);
824 actor.RegisterProperty("uOffset", Vector2::ZERO);
825 actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
830 void StitchTextMesh(std::vector<MeshRecord>& meshContainer,
831 AtlasManager::Mesh2D& newMesh,
832 Vector<Extent>& extents,
834 bool decorationlineGlyph,
835 float underlinePosition,
837 AtlasManager::AtlasSlot& slot,
838 uint32_t underlineChunkId,
839 float strikethroughPosition)
843 float left = newMesh.mVertices[0].mPosition.x;
844 float right = newMesh.mVertices[1].mPosition.x;
846 // Check to see if there's a mesh data object that references the same atlas ?
848 for(std::vector<MeshRecord>::iterator mIt = meshContainer.begin(),
849 mEndIt = meshContainer.end();
853 if(slot.mAtlasId == mIt->mAtlasId)
855 // Append the mesh to the existing mesh and adjust any extents
856 Toolkit::Internal::AtlasMeshFactory::AppendMesh(mIt->mMesh, newMesh);
858 if(decorationlineGlyph)
860 AdjustExtents(extents,
869 strikethroughPosition);
876 // No mesh data object currently exists that references this atlas, so create a new one
877 MeshRecord meshRecord;
878 meshRecord.mAtlasId = slot.mAtlasId;
879 meshRecord.mMesh = newMesh;
880 meshContainer.push_back(meshRecord);
882 if(decorationlineGlyph)
884 // Adjust extents for this new meshrecord
885 AdjustExtents(extents,
887 meshContainer.size() - 1u,
894 strikethroughPosition);
899 void AdjustExtents(Vector<Extent>& extents,
900 std::vector<MeshRecord>& meshRecords,
905 float underlinePosition,
907 uint32_t underlineChunkId,
908 float strikethroughPosition)
910 bool foundExtent = false;
911 for(Vector<Extent>::Iterator eIt = extents.Begin(),
912 eEndIt = extents.End();
916 if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId)
919 if(left < eIt->mLeft)
923 if(right > eIt->mRight)
928 if(underlinePosition > eIt->mUnderlinePosition)
930 eIt->mUnderlinePosition = underlinePosition;
932 if(lineThickness > eIt->mLineThickness)
934 eIt->mLineThickness = lineThickness;
942 extent.mRight = right;
943 extent.mBaseLine = baseLine;
944 extent.mUnderlinePosition = underlinePosition;
945 extent.mMeshRecordIndex = index;
946 extent.mUnderlineChunkId = underlineChunkId;
947 extent.mLineThickness = lineThickness;
948 extent.mStrikethroughPosition = strikethroughPosition;
949 extents.PushBack(extent);
953 void CalculateBlocksSize(const Vector<GlyphInfo>& glyphs)
955 for(Vector<GlyphInfo>::ConstIterator glyphIt = glyphs.Begin(),
956 glyphEndIt = glyphs.End();
957 glyphIt != glyphEndIt;
960 const FontId fontId = (*glyphIt).fontId;
961 bool foundFont = false;
963 for(std::vector<MaxBlockSize>::const_iterator blockIt = mBlockSizes.begin(),
964 blockEndIt = mBlockSizes.end();
965 blockIt != blockEndIt;
968 if((*blockIt).mFontId == fontId) // Different size fonts will have a different fontId
970 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize match found fontID(%u) glyphIndex(%u)\n", fontId, (*glyphIt).index);
978 FontMetrics fontMetrics;
979 mFontClient.GetFontMetrics(fontId, fontMetrics);
981 MaxBlockSize maxBlockSize;
982 maxBlockSize.mNeededBlockWidth = static_cast<uint32_t>(fontMetrics.height);
983 maxBlockSize.mNeededBlockHeight = maxBlockSize.mNeededBlockWidth;
984 maxBlockSize.mFontId = fontId;
985 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize New font with no matched blocksize, setting blocksize[%u]\n", maxBlockSize.mNeededBlockWidth);
986 mBlockSizes.push_back(maxBlockSize);
991 void GenerateUnderlines(std::vector<MeshRecord>& meshRecords,
992 Vector<Extent>& extents,
993 const Vector4& underlineColor,
994 const Text::Underline::Type& underlineType,
995 const float& dashedUnderlineWidth,
996 const float& dashedUnderlineGap)
998 AtlasManager::Mesh2D newMesh;
999 unsigned short faceIndex = 0;
1000 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1001 eEndIt = extents.End();
1005 AtlasManager::Vertex2D vert;
1006 uint32_t index = eIt->mMeshRecordIndex;
1007 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1009 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1010 float u = HALF / uv.x;
1011 float v = HALF / uv.y;
1012 float thickness = eIt->mLineThickness;
1013 float ShiftLineBy = (underlineType == Text::Underline::Type::DOUBLE) ? (thickness * ONE_AND_A_HALF) : (thickness * HALF);
1014 float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ShiftLineBy;
1015 float tlx = eIt->mLeft;
1016 float brx = eIt->mRight;
1018 if(underlineType == Text::Underline::Type::DASHED)
1020 float dashTlx = tlx;
1021 float dashBrx = tlx;
1024 AtlasManager::Mesh2D newMesh;
1025 while((dashTlx >= tlx) && (dashTlx < brx) && ((dashTlx + dashedUnderlineWidth) <= brx))
1027 dashBrx = dashTlx + dashedUnderlineWidth;
1029 //The top left edge of the underline
1030 vert.mPosition.x = dashTlx;
1031 vert.mPosition.y = baseLine;
1032 vert.mTexCoords.x = ZERO;
1033 vert.mTexCoords.y = ZERO;
1034 vert.mColor = underlineColor;
1035 newMesh.mVertices.PushBack(vert);
1037 //The top right edge of the underline
1038 vert.mPosition.x = dashBrx;
1039 vert.mPosition.y = baseLine;
1040 vert.mTexCoords.x = u;
1041 vert.mColor = underlineColor;
1042 newMesh.mVertices.PushBack(vert);
1044 //The bottom left edge of the underline
1045 vert.mPosition.x = dashTlx;
1046 vert.mPosition.y = baseLine + thickness;
1047 vert.mTexCoords.x = ZERO;
1048 vert.mTexCoords.y = v;
1049 vert.mColor = underlineColor;
1050 newMesh.mVertices.PushBack(vert);
1052 //The bottom right edge of the underline
1053 vert.mPosition.x = dashBrx;
1054 vert.mPosition.y = baseLine + thickness;
1055 vert.mTexCoords.x = u;
1056 vert.mColor = underlineColor;
1057 newMesh.mVertices.PushBack(vert);
1059 dashTlx = dashBrx + dashedUnderlineGap; // The next dash will start at the right of the current dash plus the gap
1061 // Six indices in counter clockwise winding
1062 newMesh.mIndices.PushBack(faceIndex + 1u);
1063 newMesh.mIndices.PushBack(faceIndex);
1064 newMesh.mIndices.PushBack(faceIndex + 2u);
1065 newMesh.mIndices.PushBack(faceIndex + 2u);
1066 newMesh.mIndices.PushBack(faceIndex + 3u);
1067 newMesh.mIndices.PushBack(faceIndex + 1u);
1071 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1076 // It's either SOLID or DOUBLE so we need to generate the first solid underline anyway.
1077 vert.mPosition.x = tlx;
1078 vert.mPosition.y = baseLine;
1079 vert.mTexCoords.x = ZERO;
1080 vert.mTexCoords.y = ZERO;
1081 vert.mColor = underlineColor;
1082 newMesh.mVertices.PushBack(vert);
1084 vert.mPosition.x = brx;
1085 vert.mPosition.y = baseLine;
1086 vert.mTexCoords.x = u;
1087 vert.mColor = underlineColor;
1088 newMesh.mVertices.PushBack(vert);
1090 vert.mPosition.x = tlx;
1091 vert.mPosition.y = baseLine + thickness;
1092 vert.mTexCoords.x = ZERO;
1093 vert.mTexCoords.y = v;
1094 vert.mColor = underlineColor;
1095 newMesh.mVertices.PushBack(vert);
1097 vert.mPosition.x = brx;
1098 vert.mPosition.y = baseLine + thickness;
1099 vert.mTexCoords.x = u;
1100 vert.mColor = underlineColor;
1101 newMesh.mVertices.PushBack(vert);
1103 // Six indices in counter clockwise winding
1104 newMesh.mIndices.PushBack(faceIndex + 1u);
1105 newMesh.mIndices.PushBack(faceIndex);
1106 newMesh.mIndices.PushBack(faceIndex + 2u);
1107 newMesh.mIndices.PushBack(faceIndex + 2u);
1108 newMesh.mIndices.PushBack(faceIndex + 3u);
1109 newMesh.mIndices.PushBack(faceIndex + 1u);
1112 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1114 if(underlineType == Text::Underline::Type::DOUBLE)
1116 baseLine += 2 * thickness;
1118 //The top left edge of the underline
1119 vert.mPosition.x = tlx;
1120 vert.mPosition.y = baseLine; // Vertical start of the second underline
1121 vert.mTexCoords.x = ZERO;
1122 vert.mTexCoords.y = ZERO;
1123 vert.mColor = underlineColor;
1124 newMesh.mVertices.PushBack(vert);
1126 //The top right edge of the underline
1127 vert.mPosition.x = brx;
1128 vert.mPosition.y = baseLine;
1129 vert.mTexCoords.x = u;
1130 vert.mColor = underlineColor;
1131 newMesh.mVertices.PushBack(vert);
1133 //The bottom left edge of the underline
1134 vert.mPosition.x = tlx;
1135 vert.mPosition.y = baseLine + thickness; // Vertical End of the second underline
1136 vert.mTexCoords.x = ZERO;
1137 vert.mTexCoords.y = v;
1138 vert.mColor = underlineColor;
1139 newMesh.mVertices.PushBack(vert);
1141 //The bottom right edge of the underline
1142 vert.mPosition.x = brx;
1143 vert.mPosition.y = baseLine + thickness;
1144 vert.mTexCoords.x = u;
1145 vert.mColor = underlineColor;
1146 newMesh.mVertices.PushBack(vert);
1148 // Six indices in counter clockwise winding
1149 newMesh.mIndices.PushBack(faceIndex + 1u);
1150 newMesh.mIndices.PushBack(faceIndex);
1151 newMesh.mIndices.PushBack(faceIndex + 2u);
1152 newMesh.mIndices.PushBack(faceIndex + 2u);
1153 newMesh.mIndices.PushBack(faceIndex + 3u);
1154 newMesh.mIndices.PushBack(faceIndex + 1u);
1158 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1164 void GenerateStrikethrough(std::vector<MeshRecord>& meshRecords,
1165 Vector<Extent>& extents,
1166 const Vector4& strikethroughColor)
1168 AtlasManager::Mesh2D newMesh;
1169 unsigned short faceIndex = 0;
1170 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1171 eEndIt = extents.End();
1175 AtlasManager::Vertex2D vert;
1176 uint32_t index = eIt->mMeshRecordIndex;
1177 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1179 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1180 float u = HALF / uv.x;
1181 float v = HALF / uv.y;
1182 float thickness = eIt->mLineThickness;
1183 float tlx = eIt->mLeft;
1184 float brx = eIt->mRight;
1185 float strikethroughPosition = eIt->mStrikethroughPosition;
1187 vert.mPosition.x = tlx;
1188 vert.mPosition.y = strikethroughPosition;
1189 vert.mTexCoords.x = ZERO;
1190 vert.mTexCoords.y = ZERO;
1191 vert.mColor = strikethroughColor;
1192 newMesh.mVertices.PushBack(vert);
1194 vert.mPosition.x = brx;
1195 vert.mPosition.y = strikethroughPosition;
1196 vert.mTexCoords.x = u;
1197 vert.mColor = strikethroughColor;
1198 newMesh.mVertices.PushBack(vert);
1200 vert.mPosition.x = tlx;
1201 vert.mPosition.y = strikethroughPosition + thickness;
1202 vert.mTexCoords.x = ZERO;
1203 vert.mTexCoords.y = v;
1204 vert.mColor = strikethroughColor;
1205 newMesh.mVertices.PushBack(vert);
1207 vert.mPosition.x = brx;
1208 vert.mPosition.y = strikethroughPosition + thickness;
1209 vert.mTexCoords.x = u;
1210 vert.mColor = strikethroughColor;
1211 newMesh.mVertices.PushBack(vert);
1213 // Six indices in counter clockwise winding
1214 newMesh.mIndices.PushBack(faceIndex + 1u);
1215 newMesh.mIndices.PushBack(faceIndex);
1216 newMesh.mIndices.PushBack(faceIndex + 2u);
1217 newMesh.mIndices.PushBack(faceIndex + 2u);
1218 newMesh.mIndices.PushBack(faceIndex + 3u);
1219 newMesh.mIndices.PushBack(faceIndex + 1u);
1222 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1226 Actor mActor; ///< The actor parent which renders the text
1227 AtlasGlyphManager mGlyphManager; ///< Glyph Manager to handle upload and caching
1228 TextAbstraction::FontClient mFontClient; ///< The font client used to supply glyph information
1229 Shader mShaderL8; ///< The shader for glyphs and emoji's shadows.
1230 Shader mShaderRgba; ///< The shader for emojis.
1231 std::vector<MaxBlockSize> mBlockSizes; ///< Maximum size needed to contain a glyph in a block within a new atlas
1232 Vector<TextCacheEntry> mTextCache; ///< Caches data from previous render
1233 Property::Map mQuadVertexFormat; ///< Describes the vertex format for text
1234 int mDepth; ///< DepthIndex passed by control when connect to stage
1237 Text::RendererPtr AtlasRenderer::New()
1239 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::New()\n");
1241 return Text::RendererPtr(new AtlasRenderer());
1244 Actor AtlasRenderer::Render(Text::ViewInterface& view,
1246 Property::Index animatablePropertyIndex,
1247 float& alignmentOffset,
1250 DALI_LOG_INFO(gLogFilter, Debug::General, "Text::AtlasRenderer::Render()\n");
1252 UnparentAndReset(mImpl->mActor);
1254 Length numberOfGlyphs = view.GetNumberOfGlyphs();
1256 if(numberOfGlyphs > 0u)
1258 Vector<GlyphInfo> glyphs;
1259 glyphs.Resize(numberOfGlyphs);
1261 Vector<Vector2> positions;
1262 positions.Resize(numberOfGlyphs);
1264 numberOfGlyphs = view.GetGlyphs(glyphs.Begin(),
1270 glyphs.Resize(numberOfGlyphs);
1271 positions.Resize(numberOfGlyphs);
1273 const Vector4* const colorsBuffer = view.GetColors();
1274 const ColorIndex* const colorIndicesBuffer = view.GetColorIndices();
1275 const Vector4& defaultColor = view.GetTextColor();
1277 mImpl->AddGlyphs(view,
1279 animatablePropertyIndex,
1288 /* In the case where AddGlyphs does not create a renderable Actor for example when glyphs are all whitespace create a new Actor. */
1289 /* 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. */
1292 mImpl->mActor = Actor::New();
1296 return mImpl->mActor;
1299 AtlasRenderer::AtlasRenderer()
1304 AtlasRenderer::~AtlasRenderer()
1306 mImpl->RemoveText();