2 * Copyright (c) 2022 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-metrics-helper.h>
32 #include <dali-toolkit/internal/text/glyph-run.h>
33 #include <dali-toolkit/internal/text/rendering/atlas/atlas-glyph-manager.h>
34 #include <dali-toolkit/internal/text/rendering/atlas/atlas-mesh-factory.h>
35 #include <dali-toolkit/internal/text/text-view.h>
38 using namespace Dali::Toolkit;
39 using namespace Dali::Toolkit::Text;
43 #if defined(DEBUG_ENABLED)
44 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_RENDERING");
47 const float ZERO(0.0f);
48 const float HALF(0.5f);
49 const float ONE(1.0f);
50 const float ONE_AND_A_HALF(1.5f);
51 const uint32_t DOUBLE_PIXEL_PADDING = 4u; //Padding will be added twice to Atlas
52 const uint16_t NO_OUTLINE = 0u;
55 struct AtlasRenderer::Impl
71 AtlasManager::Mesh2D mMesh;
75 * brief Struct used to generate the underline/striketthrough mesh.
76 * There is one Extent per line of text.
84 mUnderlinePosition(0.0f),
87 mUnderlineChunkId(0u),
88 mStrikethroughPosition(0.0f),
89 mStrikethroughChunkId(0u)
96 float mUnderlinePosition;
98 uint32_t mMeshRecordIndex;
99 uint32_t mUnderlineChunkId;
100 float mStrikethroughPosition;
101 uint32_t mStrikethroughChunkId;
108 mNeededBlockWidth(0),
109 mNeededBlockHeight(0)
114 uint32_t mNeededBlockWidth;
115 uint32_t mNeededBlockHeight;
127 Text::GlyphIndex mIndex;
130 struct TextCacheEntry
143 Text::GlyphIndex mIndex;
145 uint16_t mOutlineWidth;
153 mGlyphManager = AtlasGlyphManager::Get();
154 mFontClient = TextAbstraction::FontClient::Get();
156 mQuadVertexFormat["aPosition"] = Property::VECTOR2;
157 mQuadVertexFormat["aTexCoord"] = Property::VECTOR2;
158 mQuadVertexFormat["aColor"] = Property::VECTOR4;
161 bool IsGlyphUnderlined(GlyphIndex index,
162 const Vector<GlyphRun>& underlineRuns)
164 for(Vector<GlyphRun>::ConstIterator it = underlineRuns.Begin(),
165 endIt = underlineRuns.End();
169 const GlyphRun& run = *it;
171 if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs))
180 bool doGlyphHaveStrikethrough(GlyphIndex index,
181 const Vector<StrikethroughGlyphRun>& strikethroughRuns,
182 Vector4& strikethroughColor)
184 for(Vector<StrikethroughGlyphRun>::ConstIterator it = strikethroughRuns.Begin(),
185 endIt = strikethroughRuns.End();
189 const StrikethroughGlyphRun& run = *it;
191 if((run.glyphRun.glyphIndex <= index) && (index < run.glyphRun.glyphIndex + run.glyphRun.numberOfGlyphs))
195 strikethroughColor = run.color;
204 void CacheGlyph(const GlyphInfo& glyph, FontId lastFontId, const AtlasGlyphManager::GlyphStyle& style, AtlasManager::AtlasSlot& slot)
206 const Size& defaultTextAtlasSize = mFontClient.GetDefaultTextAtlasSize(); //Retrieve default size of text-atlas-block from font-client.
207 const Size& maximumTextAtlasSize = mFontClient.GetMaximumTextAtlasSize(); //Retrieve maximum size of text-atlas-block from font-client.
209 const bool glyphNotCached = !mGlyphManager.IsCached(glyph.fontId, glyph.index, style, slot); // Check FontGlyphRecord vector for entry with glyph index and fontId
211 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "AddGlyphs fontID[%u] glyphIndex[%u] [%s]\n", glyph.fontId, glyph.index, (glyphNotCached) ? "not cached" : "cached");
215 MaxBlockSize& blockSize = mBlockSizes[0u];
217 if(lastFontId != glyph.fontId)
220 // 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.
221 // CalculateBlocksSize() above ensures a block size entry exists.
222 for(std::vector<MaxBlockSize>::const_iterator it = mBlockSizes.begin(),
223 endIt = mBlockSizes.end();
227 const MaxBlockSize& blockSizeEntry = *it;
228 if(blockSizeEntry.mFontId == glyph.fontId)
230 blockSize = mBlockSizes[index];
235 // Create a new image for the glyph
238 // Whether the glyph is an outline.
239 const bool isOutline = 0u != style.outline;
241 // Whether the current glyph is a color one.
242 const bool isColorGlyph = mFontClient.IsColorGlyph(glyph.fontId, glyph.index);
244 if(!isOutline || (isOutline && !isColorGlyph))
246 // Retrieve the emoji's bitmap.
247 TextAbstraction::FontClient::GlyphBufferData glyphBufferData;
248 glyphBufferData.width = isColorGlyph ? glyph.width : 0; // Desired width and height.
249 glyphBufferData.height = isColorGlyph ? glyph.height : 0;
251 mFontClient.CreateBitmap(glyph.fontId,
253 glyph.isItalicRequired,
254 glyph.isBoldRequired,
258 // Create the pixel data.
259 bitmap = PixelData::New(glyphBufferData.buffer,
260 glyphBufferData.width * glyphBufferData.height * GetBytesPerPixel(glyphBufferData.format),
261 glyphBufferData.width,
262 glyphBufferData.height,
263 glyphBufferData.format,
264 PixelData::DELETE_ARRAY);
268 // Ensure that the next image will fit into the current block size
269 if(bitmap.GetWidth() > blockSize.mNeededBlockWidth)
271 blockSize.mNeededBlockWidth = bitmap.GetWidth();
274 if(bitmap.GetHeight() > blockSize.mNeededBlockHeight)
276 blockSize.mNeededBlockHeight = bitmap.GetHeight();
279 // If CheckAtlas in AtlasManager::Add can't fit the bitmap in the current atlas it will create a new atlas
281 // Setting the block size and size of new atlas does not mean a new one will be created. An existing atlas may still surffice.
282 uint32_t default_width = defaultTextAtlasSize.width;
283 uint32_t default_height = defaultTextAtlasSize.height;
286 (blockSize.mNeededBlockWidth >= (default_width - (DOUBLE_PIXEL_PADDING + 1u)) ||
287 blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u))) &&
288 (default_width < maximumTextAtlasSize.width &&
289 default_height < maximumTextAtlasSize.height))
291 default_width <<= 1u;
292 default_height <<= 1u;
294 mGlyphManager.SetNewAtlasSize(default_width,
296 blockSize.mNeededBlockWidth,
297 blockSize.mNeededBlockHeight);
299 // Locate a new slot for our glyph
300 mGlyphManager.Add(glyph, style, bitmap, slot); // slot will be 0 is glyph not added
306 // We have 2+ copies of the same glyph
307 mGlyphManager.AdjustReferenceCount(glyph.fontId, glyph.index, style, 1); //increment
311 void GenerateMesh(const GlyphInfo& glyph,
312 const Vector2& position,
313 const Vector4& color,
315 AtlasManager::AtlasSlot& slot,
316 bool decorationlineGlyph,
317 float currentUnderlinePosition,
318 float currentlineThickness,
319 std::vector<MeshRecord>& meshContainer,
320 Vector<TextCacheEntry>& newTextCache,
321 Vector<Extent>& extents,
322 uint32_t underlineChunkId,
324 uint32_t strikethroughChunkId)
326 // Generate mesh data for this quad, plugging in our supplied position
327 AtlasManager::Mesh2D newMesh;
328 mGlyphManager.GenerateMeshData(slot.mImageId, position, newMesh);
332 TextCacheEntry textCacheEntry;
333 textCacheEntry.mFontId = glyph.fontId;
334 textCacheEntry.mImageId = slot.mImageId;
335 textCacheEntry.mIndex = glyph.index;
336 textCacheEntry.mOutlineWidth = outline;
337 textCacheEntry.isItalic = glyph.isItalicRequired;
338 textCacheEntry.isBold = glyph.isBoldRequired;
340 newTextCache.PushBack(textCacheEntry);
343 AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin();
345 for(unsigned int index = 0u, size = newMesh.mVertices.Count();
349 AtlasManager::Vertex2D& vertex = *(verticesBuffer + index);
351 // Set the color of the vertex.
352 vertex.mColor = color;
355 // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas)
356 StitchTextMesh(meshContainer,
359 position.y + glyph.yBearing,
361 currentUnderlinePosition,
362 currentlineThickness,
365 position.y + (glyph.height * HALF),
366 strikethroughChunkId);
369 void CreateActors(const std::vector<MeshRecord>& meshContainer,
370 const Size& textSize,
371 const Vector4& color,
372 const Vector4& shadowColor,
373 const Vector2& shadowOffset,
375 Property::Index animatablePropertyIndex,
380 // Create a container actor to act as a common parent for text and shadow, to avoid color inheritence issues.
381 mActor = Actor::New();
382 mActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
383 mActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
384 mActor.SetProperty(Actor::Property::SIZE, textSize);
385 mActor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
388 for(std::vector<MeshRecord>::const_iterator it = meshContainer.begin(),
389 endIt = meshContainer.end();
393 const MeshRecord& meshRecord = *it;
395 Actor actor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_NORMAL);
397 // Whether the actor has renderers.
398 const bool hasRenderer = actor.GetRendererCount() > 0u;
400 // Create an effect if necessary
404 // Change the color of the vertices.
405 for(Vector<AtlasManager::Vertex2D>::Iterator vIt = meshRecord.mMesh.mVertices.Begin(),
406 vEndIt = meshRecord.mMesh.mVertices.End();
410 AtlasManager::Vertex2D& vertex = *vIt;
412 vertex.mColor = shadowColor;
415 Actor shadowActor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_DROP_SHADOW);
416 #if defined(DEBUG_ENABLED)
417 shadowActor.SetProperty(Dali::Actor::Property::NAME, "Text Shadow renderable actor");
419 // Offset shadow in x and y
420 shadowActor.RegisterProperty("uOffset", shadowOffset);
421 Dali::Renderer renderer(shadowActor.GetRendererAt(0));
422 int depthIndex = renderer.GetProperty<int>(Dali::Renderer::Property::DEPTH_INDEX);
423 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, depthIndex - 1);
424 mActor.Add(shadowActor);
434 void AddGlyphs(Text::ViewInterface& view,
436 Property::Index animatablePropertyIndex,
437 const Vector<Vector2>& positions,
438 const Vector<GlyphInfo>& glyphs,
439 const Vector4& defaultColor,
440 const Vector4* const colorsBuffer,
441 const ColorIndex* const colorIndicesBuffer,
445 AtlasManager::AtlasSlot slot;
449 AtlasManager::AtlasSlot slotOutline;
450 slotOutline.mImageId = 0u;
451 slotOutline.mAtlasId = 0u;
453 std::vector<MeshRecord> meshContainer;
454 std::vector<MeshRecord> meshContainerOutline;
455 Vector<Extent> extents;
456 Vector<Extent> strikethroughExtents;
459 const Vector2& textSize(view.GetLayoutSize());
460 const Vector2 halfTextSize(textSize * 0.5f);
461 const Vector2& shadowOffset(view.GetShadowOffset());
462 const Vector4& shadowColor(view.GetShadowColor());
463 const bool underlineEnabled = view.IsUnderlineEnabled();
464 const Vector4& underlineColor(view.GetUnderlineColor());
465 const float underlineHeight = view.GetUnderlineHeight();
466 const Text::Underline::Type underlineType = view.GetUnderlineType();
467 const float dashedUnderlineWidth = view.GetDashedUnderlineWidth();
468 const float dashedUnderlineGap = view.GetDashedUnderlineGap();
469 const uint16_t outlineWidth = view.GetOutlineWidth();
470 const Vector4& outlineColor(view.GetOutlineColor());
471 const bool isOutline = 0u != outlineWidth;
472 const GlyphInfo* hyphens = view.GetHyphens();
473 const Length* hyphenIndices = view.GetHyphenIndices();
474 const Length hyphensCount = view.GetHyphensCount();
475 const bool strikethroughEnabled = view.IsStrikethroughEnabled();
476 const Vector4& strikethroughColor(view.GetStrikethroughColor());
477 const float strikethroughHeight = view.GetStrikethroughHeight();
478 Vector4 currentStrikethroughColor;
479 const float characterSpacing(view.GetCharacterSpacing());
481 // Elided text info. Indices according to elided text.
482 const auto startIndexOfGlyphs = view.GetStartIndexOfElidedGlyphs();
483 const auto firstMiddleIndexOfElidedGlyphs = view.GetFirstMiddleIndexOfElidedGlyphs();
484 const auto secondMiddleIndexOfElidedGlyphs = view.GetSecondMiddleIndexOfElidedGlyphs();
486 const bool useDefaultColor = (NULL == colorsBuffer);
488 // Get the underline runs.
489 const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns();
490 Vector<GlyphRun> underlineRuns;
491 underlineRuns.Resize(numberOfUnderlineRuns);
492 view.GetUnderlineRuns(underlineRuns.Begin(),
494 numberOfUnderlineRuns);
496 // Get the strikethrough runs.
497 const Length numberOfStrikethroughRuns = view.GetNumberOfStrikethroughRuns();
498 Vector<StrikethroughGlyphRun> strikethroughRuns;
499 strikethroughRuns.Resize(numberOfStrikethroughRuns);
500 view.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns);
502 bool thereAreUnderlinedGlyphs = false;
503 bool strikethroughGlyphsExist = false;
505 float currentUnderlinePosition = ZERO;
506 float currentUnderlineThickness = underlineHeight;
507 float currentStrikethroughThickness = strikethroughHeight;
508 FontId lastFontId = 0;
509 FontId lastUnderlinedFontId = 0;
510 Style style = STYLE_NORMAL;
512 if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
514 style = STYLE_DROP_SHADOW;
517 CalculateBlocksSize(glyphs);
519 // Avoid emptying mTextCache (& removing references) until after incremented references for the new text
520 Vector<TextCacheEntry> newTextCache;
521 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
522 const Vector2* const positionsBuffer = positions.Begin();
523 const Vector2 lineOffsetPosition(minLineOffset, 0.f);
524 uint32_t hyphenIndex = 0;
526 //For septated underlined chunks. (this is for Markup case)
527 uint32_t underlineChunkId = 0u; // give id for each chunk.
528 bool isPreUnderlined = false; // status of underlined for previous glyph.
530 uint32_t strikethroughChunkId = 0u; // give id for each chunk.
531 bool isPrevGlyphStrikethrough = false; // status of strikethrough for previous glyph.
532 const Character* textBuffer = view.GetTextBuffer();
533 float calculatedAdvance = 0.f;
534 const Vector<CharacterIndex>& glyphToCharacterMap = view.GetGlyphsToCharacters();
535 const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
537 //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text
540 while((hyphenIndex < hyphensCount) && (hyphenIndices[hyphenIndex] < startIndexOfGlyphs ||
541 (hyphenIndices[hyphenIndex] > firstMiddleIndexOfElidedGlyphs && hyphenIndices[hyphenIndex] < secondMiddleIndexOfElidedGlyphs)))
547 for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
550 bool addHyphen = ((hyphenIndex < hyphensCount) && hyphenIndices && ((i + startIndexOfGlyphs) == hyphenIndices[hyphenIndex]));
551 if(addHyphen && hyphens)
553 glyph = hyphens[hyphenIndex];
558 glyph = *(glyphsBuffer + i);
561 const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns);
562 thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || isGlyphUnderlined;
564 currentStrikethroughColor = strikethroughColor;
565 const bool isStrikethroughGlyph = strikethroughEnabled || doGlyphHaveStrikethrough(i, strikethroughRuns, currentStrikethroughColor);
566 strikethroughGlyphsExist = strikethroughGlyphsExist || isStrikethroughGlyph;
568 // No operation for white space
569 if(glyph.width && glyph.height)
571 // Are we still using the same fontId as previous
572 if((isGlyphUnderlined || isStrikethroughGlyph) && (glyph.fontId != lastUnderlinedFontId))
574 // We need to fetch fresh font underline metrics
575 FontMetrics fontMetrics;
576 mFontClient.GetFontMetrics(glyph.fontId, fontMetrics);
577 currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition));
578 const float descender = ceil(fabsf(fontMetrics.descender));
580 if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000)
582 currentUnderlineThickness = fontMetrics.underlineThickness;
584 // Ensure underline will be at least a pixel high
585 if(currentUnderlineThickness < ONE)
587 currentUnderlineThickness = ONE;
591 currentUnderlineThickness = ceil(currentUnderlineThickness);
595 if(fabsf(strikethroughHeight) < Math::MACHINE_EPSILON_1000)
597 // Ensure strikethrough will be at least a pixel high
598 if(currentStrikethroughThickness < ONE)
600 currentStrikethroughThickness = ONE;
604 currentStrikethroughThickness = ceil(currentStrikethroughThickness);
608 // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
609 if(currentUnderlinePosition > descender)
611 currentUnderlinePosition = descender;
614 if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000)
616 // Move offset down by one ( EFL behavior )
617 currentUnderlinePosition = ONE;
620 lastUnderlinedFontId = glyph.fontId;
624 AtlasGlyphManager::GlyphStyle style;
625 style.isItalic = glyph.isItalicRequired;
626 style.isBold = glyph.isBoldRequired;
628 // Retrieves and caches the glyph's bitmap.
629 CacheGlyph(glyph, lastFontId, style, slot);
631 // Retrieves and caches the outline glyph's bitmap.
634 style.outline = outlineWidth;
635 CacheGlyph(glyph, lastFontId, style, slotOutline);
638 // Move the origin (0,0) of the mesh to the center of the actor
639 Vector2 position = *(positionsBuffer + i);
643 GlyphInfo tempInfo = *(glyphsBuffer + i);
644 calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + i))), characterSpacing, tempInfo.advance);
645 position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyph.xBearing;
646 position.y += tempInfo.yBearing - glyph.yBearing;
649 position = Vector2(roundf(position.x), position.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues.
651 if(0u != slot.mImageId) // invalid slot id, glyph has failed to be added to atlas
653 Vector2 positionPlusOutlineOffset = position;
656 // Add an offset to the text.
657 const float outlineWidthOffset = static_cast<float>(outlineWidth);
658 positionPlusOutlineOffset += Vector2(outlineWidthOffset, outlineWidthOffset);
661 // Get the color of the character.
662 const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndicesBuffer + i);
663 const Vector4& color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + colorIndex - 1u);
666 positionPlusOutlineOffset,
671 currentUnderlinePosition,
672 currentUnderlineThickness,
680 if(isStrikethroughGlyph)
683 positionPlusOutlineOffset,
687 strikethroughGlyphsExist,
689 currentStrikethroughThickness,
692 strikethroughExtents,
695 strikethroughChunkId);
698 lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId.
701 if(isOutline && (0u != slotOutline.mImageId)) // invalid slot id, glyph has failed to be added to atlas
709 currentUnderlinePosition,
710 currentUnderlineThickness,
711 meshContainerOutline,
719 //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case)
720 // Examples: "Hello <u>World</u> Hello <u>World</u>", "<u>World</u> Hello <u>World</u>", "<u> World</u> Hello <u>World</u>"
721 if(isPreUnderlined && (isPreUnderlined != isGlyphUnderlined))
725 //Keep status of underlined for previous glyph to check consecutive indices
726 isPreUnderlined = isGlyphUnderlined;
728 if(isPrevGlyphStrikethrough && !isStrikethroughGlyph)
730 strikethroughChunkId++;
733 isPrevGlyphStrikethrough = isStrikethroughGlyph;
742 // Now remove references for the old text
744 mTextCache.Swap(newTextCache);
746 if(thereAreUnderlinedGlyphs)
748 // Check to see if any of the text needs an underline
749 GenerateUnderlines(meshContainer, extents, underlineColor, underlineType, dashedUnderlineWidth, dashedUnderlineGap);
752 if(strikethroughGlyphsExist)
754 // Check to see if any of the text needs a strikethrough
755 GenerateStrikethrough(meshContainer, strikethroughExtents, currentStrikethroughColor);
758 // For each MeshData object, create a mesh actor and add to the renderable actor
759 bool isShadowDrawn = false;
760 if(!meshContainerOutline.empty())
762 const bool drawShadow = STYLE_DROP_SHADOW == style;
763 CreateActors(meshContainerOutline,
769 animatablePropertyIndex,
772 isShadowDrawn = drawShadow;
775 // For each MeshData object, create a mesh actor and add to the renderable actor
776 if(!meshContainer.empty())
778 const bool drawShadow = !isShadowDrawn && (STYLE_DROP_SHADOW == style);
779 CreateActors(meshContainer,
785 animatablePropertyIndex,
789 #if defined(DEBUG_ENABLED)
790 Toolkit::AtlasGlyphManager::Metrics metrics = mGlyphManager.GetMetrics();
791 DALI_LOG_INFO(gLogFilter, Debug::General, "TextAtlasRenderer::GlyphManager::GlyphCount: %i, AtlasCount: %i, TextureMemoryUse: %iK\n", metrics.mGlyphCount, metrics.mAtlasMetrics.mAtlasCount, metrics.mAtlasMetrics.mTextureMemoryUsed / 1024);
793 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%s\n", metrics.mVerboseGlyphCounts.c_str());
795 for(uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i)
797 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);
804 for(Vector<TextCacheEntry>::Iterator oldTextIter = mTextCache.Begin(); oldTextIter != mTextCache.End(); ++oldTextIter)
806 AtlasGlyphManager::GlyphStyle style;
807 style.outline = oldTextIter->mOutlineWidth;
808 style.isItalic = oldTextIter->isItalic;
809 style.isBold = oldTextIter->isBold;
810 mGlyphManager.AdjustReferenceCount(oldTextIter->mFontId, oldTextIter->mIndex, style, -1 /*decrement*/);
812 mTextCache.Resize(0);
815 Actor CreateMeshActor(Actor textControl, Property::Index animatablePropertyIndex, const Vector4& defaultColor, const MeshRecord& meshRecord, const Vector2& actorSize, Style style)
817 VertexBuffer quadVertices = VertexBuffer::New(mQuadVertexFormat);
818 quadVertices.SetData(const_cast<AtlasManager::Vertex2D*>(&meshRecord.mMesh.mVertices[0]), meshRecord.mMesh.mVertices.Size());
820 Geometry quadGeometry = Geometry::New();
821 quadGeometry.AddVertexBuffer(quadVertices);
822 quadGeometry.SetIndexBuffer(&meshRecord.mMesh.mIndices[0], meshRecord.mMesh.mIndices.Size());
824 TextureSet textureSet(mGlyphManager.GetTextures(meshRecord.mAtlasId));
826 // Choose the shader to use.
827 const bool isColorShader = (STYLE_DROP_SHADOW != style) && (Pixel::BGRA8888 == mGlyphManager.GetPixelFormat(meshRecord.mAtlasId));
831 // The glyph is an emoji and is not a shadow.
834 mShaderRgba = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_RGBA_SHADER_FRAG);
836 shader = mShaderRgba;
840 // The glyph is text or a shadow.
843 mShaderL8 = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_L8_SHADER_FRAG);
848 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "defaultColor[%f, %f, %f, %f ]\n", defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.a);
850 Dali::Property::Index shaderTextColorIndex = shader.RegisterProperty("textColorAnimatable", defaultColor);
852 if(animatablePropertyIndex != Property::INVALID_INDEX)
854 // create constraint for the animatable text's color Property with textColorAnimatable in the shader.
855 if(shaderTextColorIndex)
857 Constraint constraint = Constraint::New<Vector4>(shader, shaderTextColorIndex, EqualToConstraint());
858 constraint.AddSource(Source(textControl, animatablePropertyIndex));
864 // If not animating the text colour then set to 1's so shader uses the current vertex color
865 shader.RegisterProperty("textColorAnimatable", Vector4(1.0, 1.0, 1.0, 1.0));
868 Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, shader);
869 renderer.SetTextures(textureSet);
870 renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
871 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT + mDepth);
873 Actor actor = Actor::New();
874 #if defined(DEBUG_ENABLED)
875 actor.SetProperty(Dali::Actor::Property::NAME, "Text renderable actor");
877 actor.AddRenderer(renderer);
878 // Keep all of the origins aligned
879 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
880 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
881 actor.SetProperty(Actor::Property::SIZE, actorSize);
882 actor.RegisterProperty("uOffset", Vector2::ZERO);
883 actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
888 void StitchTextMesh(std::vector<MeshRecord>& meshContainer,
889 AtlasManager::Mesh2D& newMesh,
890 Vector<Extent>& extents,
892 bool decorationlineGlyph,
893 float underlinePosition,
895 AtlasManager::AtlasSlot& slot,
896 uint32_t underlineChunkId,
897 float strikethroughPosition,
898 uint32_t strikethroughChunkId)
902 float left = newMesh.mVertices[0].mPosition.x;
903 float right = newMesh.mVertices[1].mPosition.x;
905 // Check to see if there's a mesh data object that references the same atlas ?
907 for(std::vector<MeshRecord>::iterator mIt = meshContainer.begin(),
908 mEndIt = meshContainer.end();
912 if(slot.mAtlasId == mIt->mAtlasId)
914 // Append the mesh to the existing mesh and adjust any extents
915 Toolkit::Internal::AtlasMeshFactory::AppendMesh(mIt->mMesh, newMesh);
917 if(decorationlineGlyph)
919 AdjustExtents(extents,
928 strikethroughPosition,
929 strikethroughChunkId);
936 // No mesh data object currently exists that references this atlas, so create a new one
937 MeshRecord meshRecord;
938 meshRecord.mAtlasId = slot.mAtlasId;
939 meshRecord.mMesh = newMesh;
940 meshContainer.push_back(meshRecord);
942 if(decorationlineGlyph)
944 // Adjust extents for this new meshrecord
945 AdjustExtents(extents,
947 meshContainer.size() - 1u,
954 strikethroughPosition,
955 strikethroughChunkId);
960 void AdjustExtents(Vector<Extent>& extents,
961 std::vector<MeshRecord>& meshRecords,
966 float underlinePosition,
968 uint32_t underlineChunkId,
969 float strikethroughPosition,
970 uint32_t strikethroughChunkId)
972 bool foundExtent = false;
973 for(Vector<Extent>::Iterator eIt = extents.Begin(),
974 eEndIt = extents.End();
978 if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId && strikethroughChunkId == eIt->mStrikethroughChunkId)
981 if(left < eIt->mLeft)
985 if(right > eIt->mRight)
990 if(underlinePosition > eIt->mUnderlinePosition)
992 eIt->mUnderlinePosition = underlinePosition;
994 if(lineThickness > eIt->mLineThickness)
996 eIt->mLineThickness = lineThickness;
1003 extent.mLeft = left;
1004 extent.mRight = right;
1005 extent.mBaseLine = baseLine;
1006 extent.mUnderlinePosition = underlinePosition;
1007 extent.mMeshRecordIndex = index;
1008 extent.mUnderlineChunkId = underlineChunkId;
1009 extent.mLineThickness = lineThickness;
1010 extent.mStrikethroughPosition = strikethroughPosition;
1011 extent.mStrikethroughChunkId = strikethroughChunkId;
1012 extents.PushBack(extent);
1016 void CalculateBlocksSize(const Vector<GlyphInfo>& glyphs)
1018 for(Vector<GlyphInfo>::ConstIterator glyphIt = glyphs.Begin(),
1019 glyphEndIt = glyphs.End();
1020 glyphIt != glyphEndIt;
1023 const FontId fontId = (*glyphIt).fontId;
1024 bool foundFont = false;
1026 for(std::vector<MaxBlockSize>::const_iterator blockIt = mBlockSizes.begin(),
1027 blockEndIt = mBlockSizes.end();
1028 blockIt != blockEndIt;
1031 if((*blockIt).mFontId == fontId) // Different size fonts will have a different fontId
1033 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize match found fontID(%u) glyphIndex(%u)\n", fontId, (*glyphIt).index);
1041 FontMetrics fontMetrics;
1042 mFontClient.GetFontMetrics(fontId, fontMetrics);
1044 MaxBlockSize maxBlockSize;
1045 maxBlockSize.mNeededBlockWidth = static_cast<uint32_t>(fontMetrics.height);
1046 maxBlockSize.mNeededBlockHeight = maxBlockSize.mNeededBlockWidth;
1047 maxBlockSize.mFontId = fontId;
1048 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize New font with no matched blocksize, setting blocksize[%u]\n", maxBlockSize.mNeededBlockWidth);
1049 mBlockSizes.push_back(maxBlockSize);
1054 void GenerateUnderlines(std::vector<MeshRecord>& meshRecords,
1055 Vector<Extent>& extents,
1056 const Vector4& underlineColor,
1057 const Text::Underline::Type& underlineType,
1058 const float& dashedUnderlineWidth,
1059 const float& dashedUnderlineGap)
1061 AtlasManager::Mesh2D newMesh;
1062 unsigned short faceIndex = 0;
1063 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1064 eEndIt = extents.End();
1068 AtlasManager::Vertex2D vert;
1069 uint32_t index = eIt->mMeshRecordIndex;
1070 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1072 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1073 float u = HALF / uv.x;
1074 float v = HALF / uv.y;
1075 float thickness = eIt->mLineThickness;
1076 float ShiftLineBy = (underlineType == Text::Underline::Type::DOUBLE) ? (thickness * ONE_AND_A_HALF) : (thickness * HALF);
1077 float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ShiftLineBy;
1078 float tlx = eIt->mLeft;
1079 float brx = eIt->mRight;
1081 if(underlineType == Text::Underline::Type::DASHED)
1083 float dashTlx = tlx;
1084 float dashBrx = tlx;
1087 AtlasManager::Mesh2D newMesh;
1088 while((dashTlx >= tlx) && (dashTlx < brx) && ((dashTlx + dashedUnderlineWidth) <= brx))
1090 dashBrx = dashTlx + dashedUnderlineWidth;
1092 //The top left edge of the underline
1093 vert.mPosition.x = dashTlx;
1094 vert.mPosition.y = baseLine;
1095 vert.mTexCoords.x = ZERO;
1096 vert.mTexCoords.y = ZERO;
1097 vert.mColor = underlineColor;
1098 newMesh.mVertices.PushBack(vert);
1100 //The top right edge of the underline
1101 vert.mPosition.x = dashBrx;
1102 vert.mPosition.y = baseLine;
1103 vert.mTexCoords.x = u;
1104 vert.mColor = underlineColor;
1105 newMesh.mVertices.PushBack(vert);
1107 //The bottom left edge of the underline
1108 vert.mPosition.x = dashTlx;
1109 vert.mPosition.y = baseLine + thickness;
1110 vert.mTexCoords.x = ZERO;
1111 vert.mTexCoords.y = v;
1112 vert.mColor = underlineColor;
1113 newMesh.mVertices.PushBack(vert);
1115 //The bottom right edge of the underline
1116 vert.mPosition.x = dashBrx;
1117 vert.mPosition.y = baseLine + thickness;
1118 vert.mTexCoords.x = u;
1119 vert.mColor = underlineColor;
1120 newMesh.mVertices.PushBack(vert);
1122 dashTlx = dashBrx + dashedUnderlineGap; // The next dash will start at the right of the current dash plus the gap
1124 // Six indices in counter clockwise winding
1125 newMesh.mIndices.PushBack(faceIndex + 1u);
1126 newMesh.mIndices.PushBack(faceIndex);
1127 newMesh.mIndices.PushBack(faceIndex + 2u);
1128 newMesh.mIndices.PushBack(faceIndex + 2u);
1129 newMesh.mIndices.PushBack(faceIndex + 3u);
1130 newMesh.mIndices.PushBack(faceIndex + 1u);
1134 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1139 // It's either SOLID or DOUBLE so we need to generate the first solid underline anyway.
1140 vert.mPosition.x = tlx;
1141 vert.mPosition.y = baseLine;
1142 vert.mTexCoords.x = ZERO;
1143 vert.mTexCoords.y = ZERO;
1144 vert.mColor = underlineColor;
1145 newMesh.mVertices.PushBack(vert);
1147 vert.mPosition.x = brx;
1148 vert.mPosition.y = baseLine;
1149 vert.mTexCoords.x = u;
1150 vert.mColor = underlineColor;
1151 newMesh.mVertices.PushBack(vert);
1153 vert.mPosition.x = tlx;
1154 vert.mPosition.y = baseLine + thickness;
1155 vert.mTexCoords.x = ZERO;
1156 vert.mTexCoords.y = v;
1157 vert.mColor = underlineColor;
1158 newMesh.mVertices.PushBack(vert);
1160 vert.mPosition.x = brx;
1161 vert.mPosition.y = baseLine + thickness;
1162 vert.mTexCoords.x = u;
1163 vert.mColor = underlineColor;
1164 newMesh.mVertices.PushBack(vert);
1166 // Six indices in counter clockwise winding
1167 newMesh.mIndices.PushBack(faceIndex + 1u);
1168 newMesh.mIndices.PushBack(faceIndex);
1169 newMesh.mIndices.PushBack(faceIndex + 2u);
1170 newMesh.mIndices.PushBack(faceIndex + 2u);
1171 newMesh.mIndices.PushBack(faceIndex + 3u);
1172 newMesh.mIndices.PushBack(faceIndex + 1u);
1175 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1177 if(underlineType == Text::Underline::Type::DOUBLE)
1179 baseLine += 2 * thickness;
1181 //The top left edge of the underline
1182 vert.mPosition.x = tlx;
1183 vert.mPosition.y = baseLine; // Vertical start of the second underline
1184 vert.mTexCoords.x = ZERO;
1185 vert.mTexCoords.y = ZERO;
1186 vert.mColor = underlineColor;
1187 newMesh.mVertices.PushBack(vert);
1189 //The top right edge of the underline
1190 vert.mPosition.x = brx;
1191 vert.mPosition.y = baseLine;
1192 vert.mTexCoords.x = u;
1193 vert.mColor = underlineColor;
1194 newMesh.mVertices.PushBack(vert);
1196 //The bottom left edge of the underline
1197 vert.mPosition.x = tlx;
1198 vert.mPosition.y = baseLine + thickness; // Vertical End of the second underline
1199 vert.mTexCoords.x = ZERO;
1200 vert.mTexCoords.y = v;
1201 vert.mColor = underlineColor;
1202 newMesh.mVertices.PushBack(vert);
1204 //The bottom right edge of the underline
1205 vert.mPosition.x = brx;
1206 vert.mPosition.y = baseLine + thickness;
1207 vert.mTexCoords.x = u;
1208 vert.mColor = underlineColor;
1209 newMesh.mVertices.PushBack(vert);
1211 // Six indices in counter clockwise winding
1212 newMesh.mIndices.PushBack(faceIndex + 1u);
1213 newMesh.mIndices.PushBack(faceIndex);
1214 newMesh.mIndices.PushBack(faceIndex + 2u);
1215 newMesh.mIndices.PushBack(faceIndex + 2u);
1216 newMesh.mIndices.PushBack(faceIndex + 3u);
1217 newMesh.mIndices.PushBack(faceIndex + 1u);
1221 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1227 void GenerateStrikethrough(std::vector<MeshRecord>& meshRecords,
1228 Vector<Extent>& extents,
1229 const Vector4& strikethroughColor)
1231 AtlasManager::Mesh2D newMesh;
1232 unsigned short faceIndex = 0;
1233 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1234 eEndIt = extents.End();
1238 AtlasManager::Vertex2D vert;
1239 uint32_t index = eIt->mMeshRecordIndex;
1240 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1242 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1243 float u = HALF / uv.x;
1244 float v = HALF / uv.y;
1245 float thickness = eIt->mLineThickness;
1246 float tlx = eIt->mLeft;
1247 float brx = eIt->mRight;
1248 float strikethroughPosition = eIt->mStrikethroughPosition;
1250 vert.mPosition.x = tlx;
1251 vert.mPosition.y = strikethroughPosition;
1252 vert.mTexCoords.x = ZERO;
1253 vert.mTexCoords.y = ZERO;
1254 vert.mColor = strikethroughColor;
1255 newMesh.mVertices.PushBack(vert);
1257 vert.mPosition.x = brx;
1258 vert.mPosition.y = strikethroughPosition;
1259 vert.mTexCoords.x = u;
1260 vert.mColor = strikethroughColor;
1261 newMesh.mVertices.PushBack(vert);
1263 vert.mPosition.x = tlx;
1264 vert.mPosition.y = strikethroughPosition + thickness;
1265 vert.mTexCoords.x = ZERO;
1266 vert.mTexCoords.y = v;
1267 vert.mColor = strikethroughColor;
1268 newMesh.mVertices.PushBack(vert);
1270 vert.mPosition.x = brx;
1271 vert.mPosition.y = strikethroughPosition + thickness;
1272 vert.mTexCoords.x = u;
1273 vert.mColor = strikethroughColor;
1274 newMesh.mVertices.PushBack(vert);
1276 // Six indices in counter clockwise winding
1277 newMesh.mIndices.PushBack(faceIndex + 1u);
1278 newMesh.mIndices.PushBack(faceIndex);
1279 newMesh.mIndices.PushBack(faceIndex + 2u);
1280 newMesh.mIndices.PushBack(faceIndex + 2u);
1281 newMesh.mIndices.PushBack(faceIndex + 3u);
1282 newMesh.mIndices.PushBack(faceIndex + 1u);
1285 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1289 Actor mActor; ///< The actor parent which renders the text
1290 AtlasGlyphManager mGlyphManager; ///< Glyph Manager to handle upload and caching
1291 TextAbstraction::FontClient mFontClient; ///< The font client used to supply glyph information
1292 Shader mShaderL8; ///< The shader for glyphs and emoji's shadows.
1293 Shader mShaderRgba; ///< The shader for emojis.
1294 std::vector<MaxBlockSize> mBlockSizes; ///< Maximum size needed to contain a glyph in a block within a new atlas
1295 Vector<TextCacheEntry> mTextCache; ///< Caches data from previous render
1296 Property::Map mQuadVertexFormat; ///< Describes the vertex format for text
1297 int mDepth; ///< DepthIndex passed by control when connect to stage
1300 Text::RendererPtr AtlasRenderer::New()
1302 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::New()\n");
1304 return Text::RendererPtr(new AtlasRenderer());
1307 Actor AtlasRenderer::Render(Text::ViewInterface& view,
1309 Property::Index animatablePropertyIndex,
1310 float& alignmentOffset,
1313 DALI_LOG_INFO(gLogFilter, Debug::General, "Text::AtlasRenderer::Render()\n");
1315 UnparentAndReset(mImpl->mActor);
1317 Length numberOfGlyphs = view.GetNumberOfGlyphs();
1319 if(numberOfGlyphs > 0u)
1321 Vector<GlyphInfo> glyphs;
1322 glyphs.Resize(numberOfGlyphs);
1324 Vector<Vector2> positions;
1325 positions.Resize(numberOfGlyphs);
1327 numberOfGlyphs = view.GetGlyphs(glyphs.Begin(),
1333 glyphs.Resize(numberOfGlyphs);
1334 positions.Resize(numberOfGlyphs);
1336 const Vector4* const colorsBuffer = view.GetColors();
1337 const ColorIndex* const colorIndicesBuffer = view.GetColorIndices();
1338 const Vector4& defaultColor = view.GetTextColor();
1340 mImpl->AddGlyphs(view,
1342 animatablePropertyIndex,
1351 /* In the case where AddGlyphs does not create a renderable Actor for example when glyphs are all whitespace create a new Actor. */
1352 /* 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. */
1355 mImpl->mActor = Actor::New();
1359 return mImpl->mActor;
1362 AtlasRenderer::AtlasRenderer()
1367 AtlasRenderer::~AtlasRenderer()
1369 mImpl->RemoveText();