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>
30 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
31 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
32 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
33 #include <dali-toolkit/internal/text/glyph-run.h>
34 #include <dali-toolkit/internal/text/rendering/atlas/atlas-glyph-manager.h>
35 #include <dali-toolkit/internal/text/rendering/atlas/atlas-mesh-factory.h>
36 #include <dali-toolkit/internal/text/rendering/styles/strikethrough-helper-functions.h>
37 #include <dali-toolkit/internal/text/rendering/styles/underline-helper-functions.h>
38 #include <dali-toolkit/internal/text/text-view.h>
41 using namespace Dali::Toolkit;
42 using namespace Dali::Toolkit::Text;
46 #if defined(DEBUG_ENABLED)
47 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_RENDERING");
50 const float ZERO(0.0f);
51 const float HALF(0.5f);
52 const float ONE(1.0f);
53 const float ONE_AND_A_HALF(1.5f);
54 const uint32_t DOUBLE_PIXEL_PADDING = 4u; //Padding will be added twice to Atlas
55 const uint16_t NO_OUTLINE = 0u;
58 struct AtlasRenderer::Impl
74 AtlasManager::Mesh2D mMesh;
78 * brief Struct used to generate the underline/striketthrough mesh.
79 * There is one Extent per line of text.
87 mUnderlinePosition(0.0f),
90 mUnderlineChunkId(0u),
91 mStrikethroughPosition(0.0f),
92 mStrikethroughChunkId(0u)
99 float mUnderlinePosition;
100 float mLineThickness;
101 uint32_t mMeshRecordIndex;
102 uint32_t mUnderlineChunkId;
103 float mStrikethroughPosition;
104 uint32_t mStrikethroughChunkId;
111 mNeededBlockWidth(0),
112 mNeededBlockHeight(0)
117 uint32_t mNeededBlockWidth;
118 uint32_t mNeededBlockHeight;
130 Text::GlyphIndex mIndex;
133 struct TextCacheEntry
146 Text::GlyphIndex mIndex;
148 uint16_t mOutlineWidth;
156 mGlyphManager = AtlasGlyphManager::Get();
157 mFontClient = TextAbstraction::FontClient::Get();
159 mQuadVertexFormat["aPosition"] = Property::VECTOR2;
160 mQuadVertexFormat["aTexCoord"] = Property::VECTOR2;
161 mQuadVertexFormat["aColor"] = Property::VECTOR4;
165 doGlyphHaveStrikethrough(GlyphIndex index,
166 const Vector<StrikethroughGlyphRun>& strikethroughRuns,
167 Vector4& strikethroughColor)
169 for(Vector<StrikethroughGlyphRun>::ConstIterator it = strikethroughRuns.Begin(),
170 endIt = strikethroughRuns.End();
174 const StrikethroughGlyphRun& run = *it;
176 if((run.glyphRun.glyphIndex <= index) && (index < run.glyphRun.glyphIndex + run.glyphRun.numberOfGlyphs))
180 strikethroughColor = run.color;
189 void CacheGlyph(const GlyphInfo& glyph, FontId lastFontId, const AtlasGlyphManager::GlyphStyle& style, AtlasManager::AtlasSlot& slot)
191 const Size& defaultTextAtlasSize = mFontClient.GetDefaultTextAtlasSize(); //Retrieve default size of text-atlas-block from font-client.
192 const Size& maximumTextAtlasSize = mFontClient.GetMaximumTextAtlasSize(); //Retrieve maximum size of text-atlas-block from font-client.
194 const bool glyphNotCached = !mGlyphManager.IsCached(glyph.fontId, glyph.index, style, slot); // Check FontGlyphRecord vector for entry with glyph index and fontId
196 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "AddGlyphs fontID[%u] glyphIndex[%u] [%s]\n", glyph.fontId, glyph.index, (glyphNotCached) ? "not cached" : "cached");
200 MaxBlockSize& blockSize = mBlockSizes[0u];
202 if(lastFontId != glyph.fontId)
205 // 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.
206 // CalculateBlocksSize() above ensures a block size entry exists.
207 for(std::vector<MaxBlockSize>::const_iterator it = mBlockSizes.begin(),
208 endIt = mBlockSizes.end();
212 const MaxBlockSize& blockSizeEntry = *it;
213 if(blockSizeEntry.mFontId == glyph.fontId)
215 blockSize = mBlockSizes[index];
220 // Create a new image for the glyph
223 // Whether the glyph is an outline.
224 const bool isOutline = 0u != style.outline;
226 // Whether the current glyph is a color one.
227 const bool isColorGlyph = mFontClient.IsColorGlyph(glyph.fontId, glyph.index);
229 if(!isOutline || (isOutline && !isColorGlyph))
231 // Retrieve the emoji's bitmap.
232 TextAbstraction::FontClient::GlyphBufferData glyphBufferData;
233 glyphBufferData.width = isColorGlyph ? glyph.width : 0; // Desired width and height.
234 glyphBufferData.height = isColorGlyph ? glyph.height : 0;
236 mFontClient.CreateBitmap(glyph.fontId,
238 glyph.isItalicRequired,
239 glyph.isBoldRequired,
243 // Create the pixel data.
244 bitmap = PixelData::New(glyphBufferData.buffer,
245 glyphBufferData.width * glyphBufferData.height * GetBytesPerPixel(glyphBufferData.format),
246 glyphBufferData.width,
247 glyphBufferData.height,
248 glyphBufferData.format,
249 PixelData::DELETE_ARRAY);
253 // Ensure that the next image will fit into the current block size
254 if(bitmap.GetWidth() > blockSize.mNeededBlockWidth)
256 blockSize.mNeededBlockWidth = bitmap.GetWidth();
259 if(bitmap.GetHeight() > blockSize.mNeededBlockHeight)
261 blockSize.mNeededBlockHeight = bitmap.GetHeight();
264 // If CheckAtlas in AtlasManager::Add can't fit the bitmap in the current atlas it will create a new atlas
266 // Setting the block size and size of new atlas does not mean a new one will be created. An existing atlas may still surffice.
267 uint32_t default_width = defaultTextAtlasSize.width;
268 uint32_t default_height = defaultTextAtlasSize.height;
271 (blockSize.mNeededBlockWidth >= (default_width - (DOUBLE_PIXEL_PADDING + 1u)) ||
272 blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u))) &&
273 (default_width < maximumTextAtlasSize.width &&
274 default_height < maximumTextAtlasSize.height))
276 default_width <<= 1u;
277 default_height <<= 1u;
279 mGlyphManager.SetNewAtlasSize(default_width,
281 blockSize.mNeededBlockWidth,
282 blockSize.mNeededBlockHeight);
284 // Locate a new slot for our glyph
285 mGlyphManager.Add(glyph, style, bitmap, slot); // slot will be 0 is glyph not added
291 // We have 2+ copies of the same glyph
292 mGlyphManager.AdjustReferenceCount(glyph.fontId, glyph.index, style, 1); //increment
296 void GenerateMesh(const GlyphInfo& glyph,
297 const Vector2& position,
298 const Vector4& color,
300 AtlasManager::AtlasSlot& slot,
301 bool decorationlineGlyph,
302 float currentUnderlinePosition,
303 float currentlineThickness,
304 std::vector<MeshRecord>& meshContainer,
305 Vector<TextCacheEntry>& newTextCache,
306 Vector<Extent>& extents,
307 uint32_t underlineChunkId,
309 uint32_t strikethroughChunkId)
311 // Generate mesh data for this quad, plugging in our supplied position
312 AtlasManager::Mesh2D newMesh;
313 mGlyphManager.GenerateMeshData(slot.mImageId, position, newMesh);
317 TextCacheEntry textCacheEntry;
318 textCacheEntry.mFontId = glyph.fontId;
319 textCacheEntry.mImageId = slot.mImageId;
320 textCacheEntry.mIndex = glyph.index;
321 textCacheEntry.mOutlineWidth = outline;
322 textCacheEntry.isItalic = glyph.isItalicRequired;
323 textCacheEntry.isBold = glyph.isBoldRequired;
325 newTextCache.PushBack(textCacheEntry);
328 AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin();
330 for(unsigned int index = 0u, size = newMesh.mVertices.Count();
334 AtlasManager::Vertex2D& vertex = *(verticesBuffer + index);
336 // Set the color of the vertex.
337 vertex.mColor = color;
340 // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas)
341 StitchTextMesh(meshContainer,
344 position.y + glyph.yBearing,
346 currentUnderlinePosition,
347 currentlineThickness,
350 position.y + (glyph.height * HALF),
351 strikethroughChunkId);
354 void CreateActors(const std::vector<MeshRecord>& meshContainer,
355 const Size& textSize,
356 const Vector4& color,
357 const Vector4& shadowColor,
358 const Vector2& shadowOffset,
360 Property::Index animatablePropertyIndex,
365 // Create a container actor to act as a common parent for text and shadow, to avoid color inheritence issues.
366 mActor = Actor::New();
367 mActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
368 mActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
369 mActor.SetProperty(Actor::Property::SIZE, textSize);
370 mActor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
373 for(std::vector<MeshRecord>::const_iterator it = meshContainer.begin(),
374 endIt = meshContainer.end();
378 const MeshRecord& meshRecord = *it;
380 Actor actor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_NORMAL);
382 // Whether the actor has renderers.
383 const bool hasRenderer = actor.GetRendererCount() > 0u;
385 // Create an effect if necessary
389 // Change the color of the vertices.
390 for(Vector<AtlasManager::Vertex2D>::Iterator vIt = meshRecord.mMesh.mVertices.Begin(),
391 vEndIt = meshRecord.mMesh.mVertices.End();
395 AtlasManager::Vertex2D& vertex = *vIt;
397 vertex.mColor = shadowColor;
400 Actor shadowActor = CreateMeshActor(textControl, animatablePropertyIndex, color, meshRecord, textSize, STYLE_DROP_SHADOW);
401 #if defined(DEBUG_ENABLED)
402 shadowActor.SetProperty(Dali::Actor::Property::NAME, "Text Shadow renderable actor");
404 // Offset shadow in x and y
405 shadowActor.RegisterProperty("uOffset", shadowOffset);
406 Dali::Renderer renderer(shadowActor.GetRendererAt(0));
407 int depthIndex = renderer.GetProperty<int>(Dali::Renderer::Property::DEPTH_INDEX);
408 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, depthIndex - 1);
409 mActor.Add(shadowActor);
419 void AddGlyphs(Text::ViewInterface& view,
421 Property::Index animatablePropertyIndex,
422 const Vector<Vector2>& positions,
423 const Vector<GlyphInfo>& glyphs,
424 const Vector4& defaultColor,
425 const Vector4* const colorsBuffer,
426 const ColorIndex* const colorIndicesBuffer,
430 AtlasManager::AtlasSlot slot;
434 AtlasManager::AtlasSlot slotOutline;
435 slotOutline.mImageId = 0u;
436 slotOutline.mAtlasId = 0u;
438 std::vector<MeshRecord> meshContainer;
439 std::vector<MeshRecord> meshContainerOutline;
440 Vector<Extent> extents;
441 Vector<Extent> strikethroughExtents;
444 const Vector2& textSize(view.GetLayoutSize());
445 const Vector2 halfTextSize(textSize * 0.5f);
446 const Vector2& shadowOffset(view.GetShadowOffset());
447 const Vector4& shadowColor(view.GetShadowColor());
448 const bool underlineEnabled = view.IsUnderlineEnabled();
449 const uint16_t outlineWidth = view.GetOutlineWidth();
450 const Vector4& outlineColor(view.GetOutlineColor());
451 const bool isOutline = 0u != outlineWidth;
452 const GlyphInfo* hyphens = view.GetHyphens();
453 const Length* hyphenIndices = view.GetHyphenIndices();
454 const Length hyphensCount = view.GetHyphensCount();
455 const bool strikethroughEnabled = view.IsStrikethroughEnabled();
456 const Vector4& strikethroughColor(view.GetStrikethroughColor());
457 const float strikethroughHeight = view.GetStrikethroughHeight();
458 Vector4 currentStrikethroughColor;
459 const float characterSpacing(view.GetCharacterSpacing());
461 // Elided text info. Indices according to elided text.
462 const auto startIndexOfGlyphs = view.GetStartIndexOfElidedGlyphs();
463 const auto firstMiddleIndexOfElidedGlyphs = view.GetFirstMiddleIndexOfElidedGlyphs();
464 const auto secondMiddleIndexOfElidedGlyphs = view.GetSecondMiddleIndexOfElidedGlyphs();
466 const bool useDefaultColor = (NULL == colorsBuffer);
468 // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
469 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
471 // Get the underline runs.
472 const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns();
473 Vector<UnderlinedGlyphRun> underlineRuns;
474 underlineRuns.Resize(numberOfUnderlineRuns);
475 view.GetUnderlineRuns(underlineRuns.Begin(),
477 numberOfUnderlineRuns);
479 // Aggregate underline-style-properties from view
480 const UnderlineStyleProperties viewUnderlineProperties{view.GetUnderlineType(),
481 view.GetUnderlineColor(),
482 view.GetUnderlineHeight(),
483 view.GetDashedUnderlineGap(),
484 view.GetDashedUnderlineWidth(),
491 float maxUnderlineHeight = viewUnderlineProperties.height;
493 // Get the strikethrough runs.
494 const Length numberOfStrikethroughRuns = view.GetNumberOfStrikethroughRuns();
495 Vector<StrikethroughGlyphRun> strikethroughRuns;
496 strikethroughRuns.Resize(numberOfStrikethroughRuns);
497 view.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns);
499 bool thereAreUnderlinedGlyphs = false;
500 bool strikethroughGlyphsExist = false;
502 float currentUnderlinePosition = ZERO;
503 float currentStrikethroughHeight = strikethroughHeight;
504 float maxStrikethroughHeight = currentStrikethroughHeight;
505 FontId lastFontId = 0;
506 Style style = STYLE_NORMAL;
508 if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
510 style = STYLE_DROP_SHADOW;
513 CalculateBlocksSize(glyphs);
515 // Avoid emptying mTextCache (& removing references) until after incremented references for the new text
516 Vector<TextCacheEntry> newTextCache;
517 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
518 const Vector2* const positionsBuffer = positions.Begin();
519 const Vector2 lineOffsetPosition(minLineOffset, 0.f);
520 uint32_t hyphenIndex = 0;
522 //For septated underlined chunks. (this is for Markup case)
523 uint32_t underlineChunkId = 0u; // give id for each chunk.
524 bool isPreUnderlined = false; // status of underlined for previous glyph.
525 std::map<uint32_t, UnderlineStyleProperties> mapUnderlineChunkIdWithProperties; // mapping underlineChunkId with UnderlineStyleProperties to get properties of underlined chunk
526 UnderlineStyleProperties preUnderlineProperties = viewUnderlineProperties; // the previous UnderlineStyleProperties
528 uint32_t strikethroughChunkId = 0u; // give id for each chunk.
529 bool isPrevGlyphStrikethrough = false; // status of strikethrough for previous glyph.
530 const Character* textBuffer = view.GetTextBuffer();
531 float calculatedAdvance = 0.f;
532 const Vector<CharacterIndex>& glyphToCharacterMap = view.GetGlyphsToCharacters();
533 const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
535 //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text
538 while((hyphenIndex < hyphensCount) && (hyphenIndices[hyphenIndex] < startIndexOfGlyphs ||
539 (hyphenIndices[hyphenIndex] > firstMiddleIndexOfElidedGlyphs && hyphenIndices[hyphenIndex] < secondMiddleIndexOfElidedGlyphs)))
545 //To keep the last fontMetrics of lastDecorativeLinesFontId
546 FontId lastDecorativeLinesFontId = 0; // DecorativeLines like Undeline and Strikethrough
547 FontMetrics lastDecorativeLinesFontMetrics;
548 fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics);
550 // Iteration on glyphs
551 for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
554 bool addHyphen = ((hyphenIndex < hyphensCount) && hyphenIndices && ((i + startIndexOfGlyphs) == hyphenIndices[hyphenIndex]));
555 if(addHyphen && hyphens)
557 glyph = hyphens[hyphenIndex];
562 glyph = *(glyphsBuffer + i);
565 Vector<UnderlinedGlyphRun>::ConstIterator currentUnderlinedGlyphRunIt = underlineRuns.End();
566 const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns, currentUnderlinedGlyphRunIt);
567 const UnderlineStyleProperties currentUnderlineProperties = GetCurrentUnderlineProperties(isGlyphUnderlined, underlineRuns, currentUnderlinedGlyphRunIt, viewUnderlineProperties);
568 float currentUnderlineHeight = GetCurrentUnderlineHeight(underlineRuns, currentUnderlinedGlyphRunIt, viewUnderlineProperties.height);
569 thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || isGlyphUnderlined;
571 currentStrikethroughColor = strikethroughColor;
572 const bool isStrikethroughGlyph = strikethroughEnabled || doGlyphHaveStrikethrough(i, strikethroughRuns, currentStrikethroughColor);
573 strikethroughGlyphsExist = strikethroughGlyphsExist || isStrikethroughGlyph;
575 // No operation for white space
576 if(glyph.width && glyph.height)
578 // Check and update decorative-lines informations
579 if((isGlyphUnderlined || isStrikethroughGlyph) &&
580 ((glyph.fontId != lastDecorativeLinesFontId) || !(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties))))
582 bool isDecorativeLinesFontIdUpdated = false;
583 // Are we still using the same fontId as previous
584 if(glyph.fontId != lastDecorativeLinesFontId)
586 // We need to fetch fresh font metrics
587 lastDecorativeLinesFontId = glyph.fontId;
588 isDecorativeLinesFontIdUpdated = true;
589 fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics);
591 if(isStrikethroughGlyph || isGlyphUnderlined)
593 //The currentUnderlinePosition will be used for both Underline and/or Strikethrough
594 currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(lastDecorativeLinesFontMetrics);
598 if(isGlyphUnderlined && (isDecorativeLinesFontIdUpdated || !(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties))))
600 //If the Underline Height is changed then we need to recalculate height.
601 if(!(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties)))
603 maxUnderlineHeight = currentUnderlineHeight;
606 CalcualteUnderlineHeight(lastDecorativeLinesFontMetrics, currentUnderlineHeight, maxUnderlineHeight);
609 if(isDecorativeLinesFontIdUpdated && isStrikethroughGlyph)
611 CalcualteStrikethroughHeight(currentStrikethroughHeight, maxStrikethroughHeight);
613 } // decorative-lines
615 AtlasGlyphManager::GlyphStyle style;
616 style.isItalic = glyph.isItalicRequired;
617 style.isBold = glyph.isBoldRequired;
619 // Retrieves and caches the glyph's bitmap.
620 CacheGlyph(glyph, lastFontId, style, slot);
622 // Retrieves and caches the outline glyph's bitmap.
625 style.outline = outlineWidth;
626 CacheGlyph(glyph, lastFontId, style, slotOutline);
629 // Move the origin (0,0) of the mesh to the center of the actor
630 Vector2 position = *(positionsBuffer + i);
634 GlyphInfo tempInfo = *(glyphsBuffer + i);
635 calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + i))), characterSpacing, tempInfo.advance);
636 position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyph.xBearing;
637 position.y += tempInfo.yBearing - glyph.yBearing;
640 position = Vector2(roundf(position.x), position.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues.
642 if(0u != slot.mImageId) // invalid slot id, glyph has failed to be added to atlas
644 Vector2 positionPlusOutlineOffset = position;
647 // Add an offset to the text.
648 const float outlineWidthOffset = static_cast<float>(outlineWidth);
649 positionPlusOutlineOffset += Vector2(outlineWidthOffset, outlineWidthOffset);
652 // Get the color of the character.
653 const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndicesBuffer + i);
654 const Vector4& color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + colorIndex - 1u);
656 //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case)
657 // Examples: "Hello <u>World</u> Hello <u>World</u>", "<u>World</u> Hello <u>World</u>", "<u> World</u> Hello <u>World</u>"
658 if(isPreUnderlined && (!isGlyphUnderlined || (preUnderlineProperties != currentUnderlineProperties)))
660 mapUnderlineChunkIdWithProperties.insert(std::pair<uint32_t, UnderlineStyleProperties>(underlineChunkId, preUnderlineProperties));
664 //Keep status of underlined for previous glyph to check consecutive indices
665 isPreUnderlined = isGlyphUnderlined;
666 preUnderlineProperties = currentUnderlineProperties;
669 positionPlusOutlineOffset,
674 currentUnderlinePosition,
683 if(isStrikethroughGlyph)
686 positionPlusOutlineOffset,
690 strikethroughGlyphsExist,
692 maxStrikethroughHeight,
695 strikethroughExtents,
698 strikethroughChunkId);
701 lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId.
704 if(isOutline && (0u != slotOutline.mImageId)) // invalid slot id, glyph has failed to be added to atlas
712 currentUnderlinePosition,
714 meshContainerOutline,
722 if(isPrevGlyphStrikethrough && !isStrikethroughGlyph)
724 strikethroughChunkId++;
727 isPrevGlyphStrikethrough = isStrikethroughGlyph;
736 // Now remove references for the old text
738 mTextCache.Swap(newTextCache);
740 if(thereAreUnderlinedGlyphs)
742 // Check to see if any of the text needs an underline
743 GenerateUnderlines(meshContainer, extents, viewUnderlineProperties, mapUnderlineChunkIdWithProperties);
746 if(strikethroughGlyphsExist)
748 // Check to see if any of the text needs a strikethrough
749 GenerateStrikethrough(meshContainer, strikethroughExtents, currentStrikethroughColor);
752 // For each MeshData object, create a mesh actor and add to the renderable actor
753 bool isShadowDrawn = false;
754 if(!meshContainerOutline.empty())
756 const bool drawShadow = STYLE_DROP_SHADOW == style;
757 CreateActors(meshContainerOutline,
763 animatablePropertyIndex,
766 isShadowDrawn = drawShadow;
769 // For each MeshData object, create a mesh actor and add to the renderable actor
770 if(!meshContainer.empty())
772 const bool drawShadow = !isShadowDrawn && (STYLE_DROP_SHADOW == style);
773 CreateActors(meshContainer,
779 animatablePropertyIndex,
783 #if defined(DEBUG_ENABLED)
784 Toolkit::AtlasGlyphManager::Metrics metrics = mGlyphManager.GetMetrics();
785 DALI_LOG_INFO(gLogFilter, Debug::General, "TextAtlasRenderer::GlyphManager::GlyphCount: %i, AtlasCount: %i, TextureMemoryUse: %iK\n", metrics.mGlyphCount, metrics.mAtlasMetrics.mAtlasCount, metrics.mAtlasMetrics.mTextureMemoryUsed / 1024);
787 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%s\n", metrics.mVerboseGlyphCounts.c_str());
789 for(uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i)
791 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);
798 for(Vector<TextCacheEntry>::Iterator oldTextIter = mTextCache.Begin(); oldTextIter != mTextCache.End(); ++oldTextIter)
800 AtlasGlyphManager::GlyphStyle style;
801 style.outline = oldTextIter->mOutlineWidth;
802 style.isItalic = oldTextIter->isItalic;
803 style.isBold = oldTextIter->isBold;
804 mGlyphManager.AdjustReferenceCount(oldTextIter->mFontId, oldTextIter->mIndex, style, -1 /*decrement*/);
806 mTextCache.Resize(0);
809 Actor CreateMeshActor(Actor textControl, Property::Index animatablePropertyIndex, const Vector4& defaultColor, const MeshRecord& meshRecord, const Vector2& actorSize, Style style)
811 VertexBuffer quadVertices = VertexBuffer::New(mQuadVertexFormat);
812 quadVertices.SetData(const_cast<AtlasManager::Vertex2D*>(&meshRecord.mMesh.mVertices[0]), meshRecord.mMesh.mVertices.Size());
814 Geometry quadGeometry = Geometry::New();
815 quadGeometry.AddVertexBuffer(quadVertices);
816 quadGeometry.SetIndexBuffer(&meshRecord.mMesh.mIndices[0], meshRecord.mMesh.mIndices.Size());
818 TextureSet textureSet(mGlyphManager.GetTextures(meshRecord.mAtlasId));
820 // Choose the shader to use.
821 const bool isColorShader = (STYLE_DROP_SHADOW != style) && (Pixel::BGRA8888 == mGlyphManager.GetPixelFormat(meshRecord.mAtlasId));
825 // The glyph is an emoji and is not a shadow.
828 mShaderRgba = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_RGBA_SHADER_FRAG);
830 shader = mShaderRgba;
834 // The glyph is text or a shadow.
837 mShaderL8 = Shader::New(SHADER_TEXT_ATLAS_SHADER_VERT, SHADER_TEXT_ATLAS_L8_SHADER_FRAG);
842 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "defaultColor[%f, %f, %f, %f ]\n", defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.a);
844 Dali::Property::Index shaderTextColorIndex = shader.RegisterProperty("textColorAnimatable", defaultColor);
846 if(animatablePropertyIndex != Property::INVALID_INDEX)
848 // create constraint for the animatable text's color Property with textColorAnimatable in the shader.
849 if(shaderTextColorIndex)
851 Constraint constraint = Constraint::New<Vector4>(shader, shaderTextColorIndex, EqualToConstraint());
852 constraint.AddSource(Source(textControl, animatablePropertyIndex));
858 // If not animating the text colour then set to 1's so shader uses the current vertex color
859 shader.RegisterProperty("textColorAnimatable", Vector4(1.0, 1.0, 1.0, 1.0));
862 Dali::Renderer renderer = Dali::Renderer::New(quadGeometry, shader);
863 renderer.SetTextures(textureSet);
864 renderer.SetProperty(Dali::Renderer::Property::BLEND_MODE, BlendMode::ON);
865 renderer.SetProperty(Dali::Renderer::Property::DEPTH_INDEX, DepthIndex::CONTENT + mDepth);
867 Actor actor = Actor::New();
868 #if defined(DEBUG_ENABLED)
869 actor.SetProperty(Dali::Actor::Property::NAME, "Text renderable actor");
871 actor.AddRenderer(renderer);
872 // Keep all of the origins aligned
873 actor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
874 actor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
875 actor.SetProperty(Actor::Property::SIZE, actorSize);
876 actor.RegisterProperty("uOffset", Vector2::ZERO);
877 actor.SetProperty(Actor::Property::COLOR_MODE, USE_OWN_MULTIPLY_PARENT_COLOR);
882 void StitchTextMesh(std::vector<MeshRecord>& meshContainer,
883 AtlasManager::Mesh2D& newMesh,
884 Vector<Extent>& extents,
886 bool decorationlineGlyph,
887 float underlinePosition,
889 AtlasManager::AtlasSlot& slot,
890 uint32_t underlineChunkId,
891 float strikethroughPosition,
892 uint32_t strikethroughChunkId)
896 float left = newMesh.mVertices[0].mPosition.x;
897 float right = newMesh.mVertices[1].mPosition.x;
899 // Check to see if there's a mesh data object that references the same atlas ?
901 for(std::vector<MeshRecord>::iterator mIt = meshContainer.begin(),
902 mEndIt = meshContainer.end();
906 if(slot.mAtlasId == mIt->mAtlasId)
908 // Append the mesh to the existing mesh and adjust any extents
909 Toolkit::Internal::AtlasMeshFactory::AppendMesh(mIt->mMesh, newMesh);
911 if(decorationlineGlyph)
913 AdjustExtents(extents,
922 strikethroughPosition,
923 strikethroughChunkId);
930 // No mesh data object currently exists that references this atlas, so create a new one
931 MeshRecord meshRecord;
932 meshRecord.mAtlasId = slot.mAtlasId;
933 meshRecord.mMesh = newMesh;
934 meshContainer.push_back(meshRecord);
936 if(decorationlineGlyph)
938 // Adjust extents for this new meshrecord
939 AdjustExtents(extents,
941 meshContainer.size() - 1u,
948 strikethroughPosition,
949 strikethroughChunkId);
954 void AdjustExtents(Vector<Extent>& extents,
955 std::vector<MeshRecord>& meshRecords,
960 float underlinePosition,
962 uint32_t underlineChunkId,
963 float strikethroughPosition,
964 uint32_t strikethroughChunkId)
966 bool foundExtent = false;
967 for(Vector<Extent>::Iterator eIt = extents.Begin(),
968 eEndIt = extents.End();
972 if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId && strikethroughChunkId == eIt->mStrikethroughChunkId)
975 if(left < eIt->mLeft)
979 if(right > eIt->mRight)
984 if(underlinePosition > eIt->mUnderlinePosition)
986 eIt->mUnderlinePosition = underlinePosition;
988 if(lineThickness > eIt->mLineThickness)
990 eIt->mLineThickness = lineThickness;
998 extent.mRight = right;
999 extent.mBaseLine = baseLine;
1000 extent.mUnderlinePosition = underlinePosition;
1001 extent.mMeshRecordIndex = index;
1002 extent.mUnderlineChunkId = underlineChunkId;
1003 extent.mLineThickness = lineThickness;
1004 extent.mStrikethroughPosition = strikethroughPosition;
1005 extent.mStrikethroughChunkId = strikethroughChunkId;
1006 extents.PushBack(extent);
1010 void CalculateBlocksSize(const Vector<GlyphInfo>& glyphs)
1012 for(Vector<GlyphInfo>::ConstIterator glyphIt = glyphs.Begin(),
1013 glyphEndIt = glyphs.End();
1014 glyphIt != glyphEndIt;
1017 const FontId fontId = (*glyphIt).fontId;
1018 bool foundFont = false;
1020 for(std::vector<MaxBlockSize>::const_iterator blockIt = mBlockSizes.begin(),
1021 blockEndIt = mBlockSizes.end();
1022 blockIt != blockEndIt;
1025 if((*blockIt).mFontId == fontId) // Different size fonts will have a different fontId
1027 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize match found fontID(%u) glyphIndex(%u)\n", fontId, (*glyphIt).index);
1035 FontMetrics fontMetrics;
1036 mFontClient.GetFontMetrics(fontId, fontMetrics);
1038 MaxBlockSize maxBlockSize;
1039 maxBlockSize.mNeededBlockWidth = static_cast<uint32_t>(fontMetrics.height);
1040 maxBlockSize.mNeededBlockHeight = maxBlockSize.mNeededBlockWidth;
1041 maxBlockSize.mFontId = fontId;
1042 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::CalculateBlocksSize New font with no matched blocksize, setting blocksize[%u]\n", maxBlockSize.mNeededBlockWidth);
1043 mBlockSizes.push_back(maxBlockSize);
1048 void GenerateUnderlines(std::vector<MeshRecord>& meshRecords,
1049 Vector<Extent>& extents,
1050 const UnderlineStyleProperties& viewUnderlineProperties,
1051 const std::map<uint32_t, UnderlineStyleProperties>& mapUnderlineChunkIdWithProperties)
1053 AtlasManager::Mesh2D newMesh;
1054 unsigned short faceIndex = 0;
1056 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1057 eEndIt = extents.End();
1061 AtlasManager::Vertex2D vert;
1062 uint32_t index = eIt->mMeshRecordIndex;
1063 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1065 auto pairUnderlineChunkIdWithProperties = mapUnderlineChunkIdWithProperties.find(eIt->mUnderlineChunkId);
1067 const UnderlineStyleProperties underlineProperties = (pairUnderlineChunkIdWithProperties == mapUnderlineChunkIdWithProperties.end())
1068 ? viewUnderlineProperties
1069 : pairUnderlineChunkIdWithProperties->second;
1071 const Vector4& underlineColor = underlineProperties.colorDefined ? underlineProperties.color : viewUnderlineProperties.color;
1072 const Text::Underline::Type& underlineType = underlineProperties.typeDefined ? underlineProperties.type : viewUnderlineProperties.type;
1073 const float& dashedUnderlineGap = underlineProperties.dashGapDefined ? underlineProperties.dashGap : viewUnderlineProperties.dashGap;
1074 const float& dashedUnderlineWidth = underlineProperties.dashWidthDefined ? underlineProperties.dashWidth : viewUnderlineProperties.dashWidth;
1076 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1077 float u = HALF / uv.x;
1078 float v = HALF / uv.y;
1079 float thickness = eIt->mLineThickness;
1080 float ShiftLineBy = (underlineType == Text::Underline::Type::DOUBLE) ? (thickness * ONE_AND_A_HALF) : (thickness * HALF);
1081 float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ShiftLineBy;
1082 float tlx = eIt->mLeft;
1083 float brx = eIt->mRight;
1085 if(underlineType == Text::Underline::Type::DASHED)
1087 float dashTlx = tlx;
1088 float dashBrx = tlx;
1090 while((dashTlx >= tlx) && (dashTlx < brx) && ((dashTlx + dashedUnderlineWidth) <= brx))
1092 dashBrx = dashTlx + dashedUnderlineWidth;
1094 //The top left edge of the underline
1095 vert.mPosition.x = dashTlx;
1096 vert.mPosition.y = baseLine;
1097 vert.mTexCoords.x = ZERO;
1098 vert.mTexCoords.y = ZERO;
1099 vert.mColor = underlineColor;
1100 newMesh.mVertices.PushBack(vert);
1102 //The top right edge of the underline
1103 vert.mPosition.x = dashBrx;
1104 vert.mPosition.y = baseLine;
1105 vert.mTexCoords.x = u;
1106 vert.mColor = underlineColor;
1107 newMesh.mVertices.PushBack(vert);
1109 //The bottom left edge of the underline
1110 vert.mPosition.x = dashTlx;
1111 vert.mPosition.y = baseLine + thickness;
1112 vert.mTexCoords.x = ZERO;
1113 vert.mTexCoords.y = v;
1114 vert.mColor = underlineColor;
1115 newMesh.mVertices.PushBack(vert);
1117 //The bottom right edge of the underline
1118 vert.mPosition.x = dashBrx;
1119 vert.mPosition.y = baseLine + thickness;
1120 vert.mTexCoords.x = u;
1121 vert.mColor = underlineColor;
1122 newMesh.mVertices.PushBack(vert);
1124 dashTlx = dashBrx + dashedUnderlineGap; // The next dash will start at the right of the current dash plus the gap
1126 // Six indices in counter clockwise winding
1127 newMesh.mIndices.PushBack(faceIndex + 1u);
1128 newMesh.mIndices.PushBack(faceIndex);
1129 newMesh.mIndices.PushBack(faceIndex + 2u);
1130 newMesh.mIndices.PushBack(faceIndex + 2u);
1131 newMesh.mIndices.PushBack(faceIndex + 3u);
1132 newMesh.mIndices.PushBack(faceIndex + 1u);
1136 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1141 // It's either SOLID or DOUBLE so we need to generate the first solid underline anyway.
1142 vert.mPosition.x = tlx;
1143 vert.mPosition.y = baseLine;
1144 vert.mTexCoords.x = ZERO;
1145 vert.mTexCoords.y = ZERO;
1146 vert.mColor = underlineColor;
1147 newMesh.mVertices.PushBack(vert);
1149 vert.mPosition.x = brx;
1150 vert.mPosition.y = baseLine;
1151 vert.mTexCoords.x = u;
1152 vert.mColor = underlineColor;
1153 newMesh.mVertices.PushBack(vert);
1155 vert.mPosition.x = tlx;
1156 vert.mPosition.y = baseLine + thickness;
1157 vert.mTexCoords.x = ZERO;
1158 vert.mTexCoords.y = v;
1159 vert.mColor = underlineColor;
1160 newMesh.mVertices.PushBack(vert);
1162 vert.mPosition.x = brx;
1163 vert.mPosition.y = baseLine + thickness;
1164 vert.mTexCoords.x = u;
1165 vert.mColor = underlineColor;
1166 newMesh.mVertices.PushBack(vert);
1168 // Six indices in counter clockwise winding
1169 newMesh.mIndices.PushBack(faceIndex + 1u);
1170 newMesh.mIndices.PushBack(faceIndex);
1171 newMesh.mIndices.PushBack(faceIndex + 2u);
1172 newMesh.mIndices.PushBack(faceIndex + 2u);
1173 newMesh.mIndices.PushBack(faceIndex + 3u);
1174 newMesh.mIndices.PushBack(faceIndex + 1u);
1177 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1179 if(underlineType == Text::Underline::Type::DOUBLE)
1181 baseLine += 2 * thickness;
1183 //The top left edge of the underline
1184 vert.mPosition.x = tlx;
1185 vert.mPosition.y = baseLine; // Vertical start of the second underline
1186 vert.mTexCoords.x = ZERO;
1187 vert.mTexCoords.y = ZERO;
1188 vert.mColor = underlineColor;
1189 newMesh.mVertices.PushBack(vert);
1191 //The top right edge of the underline
1192 vert.mPosition.x = brx;
1193 vert.mPosition.y = baseLine;
1194 vert.mTexCoords.x = u;
1195 vert.mColor = underlineColor;
1196 newMesh.mVertices.PushBack(vert);
1198 //The bottom left edge of the underline
1199 vert.mPosition.x = tlx;
1200 vert.mPosition.y = baseLine + thickness; // Vertical End of the second underline
1201 vert.mTexCoords.x = ZERO;
1202 vert.mTexCoords.y = v;
1203 vert.mColor = underlineColor;
1204 newMesh.mVertices.PushBack(vert);
1206 //The bottom right edge of the underline
1207 vert.mPosition.x = brx;
1208 vert.mPosition.y = baseLine + thickness;
1209 vert.mTexCoords.x = u;
1210 vert.mColor = underlineColor;
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);
1223 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1229 void GenerateStrikethrough(std::vector<MeshRecord>& meshRecords,
1230 Vector<Extent>& extents,
1231 const Vector4& strikethroughColor)
1233 AtlasManager::Mesh2D newMesh;
1234 unsigned short faceIndex = 0;
1235 for(Vector<Extent>::ConstIterator eIt = extents.Begin(),
1236 eEndIt = extents.End();
1240 AtlasManager::Vertex2D vert;
1241 uint32_t index = eIt->mMeshRecordIndex;
1242 Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
1244 // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
1245 float u = HALF / uv.x;
1246 float v = HALF / uv.y;
1247 float thickness = eIt->mLineThickness;
1248 float tlx = eIt->mLeft;
1249 float brx = eIt->mRight;
1250 float strikethroughPosition = eIt->mStrikethroughPosition;
1252 vert.mPosition.x = tlx;
1253 vert.mPosition.y = strikethroughPosition;
1254 vert.mTexCoords.x = ZERO;
1255 vert.mTexCoords.y = ZERO;
1256 vert.mColor = strikethroughColor;
1257 newMesh.mVertices.PushBack(vert);
1259 vert.mPosition.x = brx;
1260 vert.mPosition.y = strikethroughPosition;
1261 vert.mTexCoords.x = u;
1262 vert.mColor = strikethroughColor;
1263 newMesh.mVertices.PushBack(vert);
1265 vert.mPosition.x = tlx;
1266 vert.mPosition.y = strikethroughPosition + thickness;
1267 vert.mTexCoords.x = ZERO;
1268 vert.mTexCoords.y = v;
1269 vert.mColor = strikethroughColor;
1270 newMesh.mVertices.PushBack(vert);
1272 vert.mPosition.x = brx;
1273 vert.mPosition.y = strikethroughPosition + thickness;
1274 vert.mTexCoords.x = u;
1275 vert.mColor = strikethroughColor;
1276 newMesh.mVertices.PushBack(vert);
1278 // Six indices in counter clockwise winding
1279 newMesh.mIndices.PushBack(faceIndex + 1u);
1280 newMesh.mIndices.PushBack(faceIndex);
1281 newMesh.mIndices.PushBack(faceIndex + 2u);
1282 newMesh.mIndices.PushBack(faceIndex + 2u);
1283 newMesh.mIndices.PushBack(faceIndex + 3u);
1284 newMesh.mIndices.PushBack(faceIndex + 1u);
1287 Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
1291 Actor mActor; ///< The actor parent which renders the text
1292 AtlasGlyphManager mGlyphManager; ///< Glyph Manager to handle upload and caching
1293 TextAbstraction::FontClient mFontClient; ///< The font client used to supply glyph information
1294 Shader mShaderL8; ///< The shader for glyphs and emoji's shadows.
1295 Shader mShaderRgba; ///< The shader for emojis.
1296 std::vector<MaxBlockSize> mBlockSizes; ///< Maximum size needed to contain a glyph in a block within a new atlas
1297 Vector<TextCacheEntry> mTextCache; ///< Caches data from previous render
1298 Property::Map mQuadVertexFormat; ///< Describes the vertex format for text
1299 int mDepth; ///< DepthIndex passed by control when connect to stage
1302 Text::RendererPtr AtlasRenderer::New()
1304 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Text::AtlasRenderer::New()\n");
1306 return Text::RendererPtr(new AtlasRenderer());
1309 Actor AtlasRenderer::Render(Text::ViewInterface& view,
1311 Property::Index animatablePropertyIndex,
1312 float& alignmentOffset,
1315 DALI_LOG_INFO(gLogFilter, Debug::General, "Text::AtlasRenderer::Render()\n");
1317 UnparentAndReset(mImpl->mActor);
1319 Length numberOfGlyphs = view.GetNumberOfGlyphs();
1321 if(numberOfGlyphs > 0u)
1323 Vector<GlyphInfo> glyphs;
1324 glyphs.Resize(numberOfGlyphs);
1326 Vector<Vector2> positions;
1327 positions.Resize(numberOfGlyphs);
1329 numberOfGlyphs = view.GetGlyphs(glyphs.Begin(),
1335 glyphs.Resize(numberOfGlyphs);
1336 positions.Resize(numberOfGlyphs);
1338 const Vector4* const colorsBuffer = view.GetColors();
1339 const ColorIndex* const colorIndicesBuffer = view.GetColorIndices();
1340 const Vector4& defaultColor = view.GetTextColor();
1342 mImpl->AddGlyphs(view,
1344 animatablePropertyIndex,
1353 /* In the case where AddGlyphs does not create a renderable Actor for example when glyphs are all whitespace create a new Actor. */
1354 /* 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. */
1357 mImpl->mActor = Actor::New();
1361 return mImpl->mActor;
1364 AtlasRenderer::AtlasRenderer()
1369 AtlasRenderer::~AtlasRenderer()
1371 mImpl->RemoveText();