X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Frendering%2Fatlas%2Ftext-atlas-renderer.cpp;h=d408450a66198f3c1f20ee79c0481fc31723c600;hp=6b1d82b970721b7a809235307252864676f1ef23;hb=7008ea12306b251a506e2ebbf0d85b6a47f1be3d;hpb=2ca1c3856ce848a94f54444f1014a820e91ee207 diff --git a/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp b/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp index 6b1d82b..d408450 100644 --- a/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp +++ b/dali-toolkit/internal/text/rendering/atlas/text-atlas-renderer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,17 @@ #include #include #include +#include // INTERNAL INCLUDES #include #include +#include #include #include #include +#include +#include #include using namespace Dali; @@ -46,8 +50,8 @@ Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT const float ZERO(0.0f); const float HALF(0.5f); const float ONE(1.0f); -const uint32_t DEFAULT_ATLAS_WIDTH = 512u; -const uint32_t DEFAULT_ATLAS_HEIGHT = 512u; +const float ONE_AND_A_HALF(1.5f); +const uint32_t DOUBLE_PIXEL_PADDING = 4u; //Padding will be added twice to Atlas const uint16_t NO_OUTLINE = 0u; } // namespace @@ -71,7 +75,7 @@ struct AtlasRenderer::Impl }; /** - * brief Struct used to generate the underline mesh. + * brief Struct used to generate the underline/striketthrough mesh. * There is one Extent per line of text. */ struct Extent @@ -81,8 +85,11 @@ struct AtlasRenderer::Impl mLeft(0.0f), mRight(0.0f), mUnderlinePosition(0.0f), - mUnderlineThickness(0.0f), - mMeshRecordIndex(0u) + mLineThickness(0.0f), + mMeshRecordIndex(0u), + mUnderlineChunkId(0u), + mStrikethroughPosition(0.0f), + mStrikethroughChunkId(0u) { } @@ -90,8 +97,11 @@ struct AtlasRenderer::Impl float mLeft; float mRight; float mUnderlinePosition; - float mUnderlineThickness; + float mLineThickness; uint32_t mMeshRecordIndex; + uint32_t mUnderlineChunkId; + float mStrikethroughPosition; + uint32_t mStrikethroughChunkId; }; struct MaxBlockSize @@ -151,27 +161,11 @@ struct AtlasRenderer::Impl mQuadVertexFormat["aColor"] = Property::VECTOR4; } - bool IsGlyphUnderlined(GlyphIndex index, - const Vector& underlineRuns) - { - for(Vector::ConstIterator it = underlineRuns.Begin(), - endIt = underlineRuns.End(); - it != endIt; - ++it) - { - const GlyphRun& run = *it; - - if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs)) - { - return true; - } - } - - return false; - } - void CacheGlyph(const GlyphInfo& glyph, FontId lastFontId, const AtlasGlyphManager::GlyphStyle& style, AtlasManager::AtlasSlot& slot) { + const Size& defaultTextAtlasSize = mFontClient.GetDefaultTextAtlasSize(); //Retrieve default size of text-atlas-block from font-client. + const Size& maximumTextAtlasSize = mFontClient.GetMaximumTextAtlasSize(); //Retrieve maximum size of text-atlas-block from font-client. + const bool glyphNotCached = !mGlyphManager.IsCached(glyph.fontId, glyph.index, style, slot); // Check FontGlyphRecord vector for entry with glyph index and fontId DALI_LOG_INFO(gLogFilter, Debug::Verbose, "AddGlyphs fontID[%u] glyphIndex[%u] [%s]\n", glyph.fontId, glyph.index, (glyphNotCached) ? "not cached" : "cached"); @@ -227,7 +221,7 @@ struct AtlasRenderer::Impl glyphBufferData.width, glyphBufferData.height, glyphBufferData.format, - PixelData::DELETE_ARRAY); + PixelData::FREE); if(bitmap) { @@ -245,8 +239,20 @@ struct AtlasRenderer::Impl // If CheckAtlas in AtlasManager::Add can't fit the bitmap in the current atlas it will create a new atlas // Setting the block size and size of new atlas does not mean a new one will be created. An existing atlas may still surffice. - mGlyphManager.SetNewAtlasSize(DEFAULT_ATLAS_WIDTH, - DEFAULT_ATLAS_HEIGHT, + uint32_t default_width = defaultTextAtlasSize.width; + uint32_t default_height = defaultTextAtlasSize.height; + + while( + (blockSize.mNeededBlockWidth >= (default_width - (DOUBLE_PIXEL_PADDING + 1u)) || + blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u))) && + (default_width < maximumTextAtlasSize.width && + default_height < maximumTextAtlasSize.height)) + { + default_width <<= 1u; + default_height <<= 1u; + } + mGlyphManager.SetNewAtlasSize(default_width, + default_height, blockSize.mNeededBlockWidth, blockSize.mNeededBlockHeight); @@ -267,26 +273,32 @@ struct AtlasRenderer::Impl const Vector4& color, uint16_t outline, AtlasManager::AtlasSlot& slot, - bool underlineGlyph, + bool decorationlineGlyph, float currentUnderlinePosition, - float currentUnderlineThickness, + float currentlineThickness, std::vector& meshContainer, Vector& newTextCache, - Vector& extents) + Vector& extents, + uint32_t underlineChunkId, + bool isGlyphCached, + uint32_t strikethroughChunkId) { // Generate mesh data for this quad, plugging in our supplied position AtlasManager::Mesh2D newMesh; mGlyphManager.GenerateMeshData(slot.mImageId, position, newMesh); - TextCacheEntry textCacheEntry; - textCacheEntry.mFontId = glyph.fontId; - textCacheEntry.mImageId = slot.mImageId; - textCacheEntry.mIndex = glyph.index; - textCacheEntry.mOutlineWidth = outline; - textCacheEntry.isItalic = glyph.isItalicRequired; - textCacheEntry.isBold = glyph.isBoldRequired; - - newTextCache.PushBack(textCacheEntry); + if(!isGlyphCached) + { + TextCacheEntry textCacheEntry; + textCacheEntry.mFontId = glyph.fontId; + textCacheEntry.mImageId = slot.mImageId; + textCacheEntry.mIndex = glyph.index; + textCacheEntry.mOutlineWidth = outline; + textCacheEntry.isItalic = glyph.isItalicRequired; + textCacheEntry.isBold = glyph.isBoldRequired; + + newTextCache.PushBack(textCacheEntry); + } AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin(); @@ -300,15 +312,22 @@ struct AtlasRenderer::Impl vertex.mColor = color; } + // Since Free Type font doesn't contain the strikethrough-position property, + // strikethrough position will be calculated by moving the underline position upwards by half the value of the line height. + float strikethroughStartingYPosition = (position.y + glyph.yBearing + currentUnderlinePosition) - ((glyph.height) * HALF); + // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas) StitchTextMesh(meshContainer, newMesh, extents, position.y + glyph.yBearing, - underlineGlyph, + decorationlineGlyph, currentUnderlinePosition, - currentUnderlineThickness, - slot); + currentlineThickness, + slot, + underlineChunkId, + strikethroughStartingYPosition, + strikethroughChunkId); } void CreateActors(const std::vector& meshContainer, @@ -398,36 +417,73 @@ struct AtlasRenderer::Impl std::vector meshContainer; std::vector meshContainerOutline; Vector extents; + Vector strikethroughExtents; mDepth = depth; - const Vector2& textSize(view.GetLayoutSize()); - const Vector2 halfTextSize(textSize * 0.5f); - const Vector2& shadowOffset(view.GetShadowOffset()); - const Vector4& shadowColor(view.GetShadowColor()); - const bool underlineEnabled = view.IsUnderlineEnabled(); - const Vector4& underlineColor(view.GetUnderlineColor()); - const float underlineHeight = view.GetUnderlineHeight(); - const uint16_t outlineWidth = view.GetOutlineWidth(); - const Vector4& outlineColor(view.GetOutlineColor()); - const bool isOutline = 0u != outlineWidth; + const Vector2& textSize(view.GetLayoutSize()); + const Vector2 halfTextSize(textSize * 0.5f); + const Vector2& shadowOffset(view.GetShadowOffset()); + const Vector4& shadowColor(view.GetShadowColor()); + const bool underlineEnabled = view.IsUnderlineEnabled(); + const uint16_t outlineWidth = view.GetOutlineWidth(); + const Vector4& outlineColor(view.GetOutlineColor()); + const bool isOutline = 0u != outlineWidth; + const GlyphInfo* hyphens = view.GetHyphens(); + const Length* hyphenIndices = view.GetHyphenIndices(); + const Length hyphensCount = view.GetHyphensCount(); + const bool strikethroughEnabled = view.IsStrikethroughEnabled(); + const float characterSpacing(view.GetCharacterSpacing()); + + // Elided text info. Indices according to elided text. + const auto startIndexOfGlyphs = view.GetStartIndexOfElidedGlyphs(); + const auto firstMiddleIndexOfElidedGlyphs = view.GetFirstMiddleIndexOfElidedGlyphs(); + const auto secondMiddleIndexOfElidedGlyphs = view.GetSecondMiddleIndexOfElidedGlyphs(); const bool useDefaultColor = (NULL == colorsBuffer); + // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs. + TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get(); + // Get the underline runs. - const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns(); - Vector underlineRuns; + const Length numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns(); + Vector underlineRuns; underlineRuns.Resize(numberOfUnderlineRuns); view.GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns); - bool thereAreUnderlinedGlyphs = false; - - float currentUnderlinePosition = ZERO; - float currentUnderlineThickness = underlineHeight; - FontId lastFontId = 0; - FontId lastUnderlinedFontId = 0; - Style style = STYLE_NORMAL; + // Aggregate underline-style-properties from view + const UnderlineStyleProperties viewUnderlineProperties{view.GetUnderlineType(), + view.GetUnderlineColor(), + view.GetUnderlineHeight(), + view.GetDashedUnderlineGap(), + view.GetDashedUnderlineWidth(), + true, + true, + true, + true, + true}; + + float maxUnderlineHeight = viewUnderlineProperties.height; + + // Get the strikethrough runs. + const Length numberOfStrikethroughRuns = view.GetNumberOfStrikethroughRuns(); + Vector strikethroughRuns; + strikethroughRuns.Resize(numberOfStrikethroughRuns); + view.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns); + + const StrikethroughStyleProperties viewStrikethroughProperties{view.GetStrikethroughColor(), + view.GetStrikethroughHeight(), + true, + true}; + + float maxStrikethroughHeight = viewStrikethroughProperties.height; + + FontId lastFontId = 0; + Style style = STYLE_NORMAL; + float currentUnderlinePosition = ZERO; + bool thereAreUnderlinedGlyphs = false; + bool thereAreStrikethroughGlyphs = false; if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1) { @@ -441,54 +497,111 @@ struct AtlasRenderer::Impl const GlyphInfo* const glyphsBuffer = glyphs.Begin(); const Vector2* const positionsBuffer = positions.Begin(); const Vector2 lineOffsetPosition(minLineOffset, 0.f); + uint32_t hyphenIndex = 0; + + //For septated underlined chunks. (this is for Markup case) + uint32_t underlineChunkId = 0u; // give id for each chunk. + bool isPreUnderlined = false; // status of underlined for previous glyph. + std::map mapUnderlineChunkIdWithProperties; // mapping underlineChunkId with UnderlineStyleProperties to get properties of underlined chunk + UnderlineStyleProperties preUnderlineProperties = viewUnderlineProperties; // the previous UnderlineStyleProperties + + //For septated strikethrough chunks. (this is for Markup case) + uint32_t strikethroughChunkId = 0u; // give id for each chunk. + bool isPreStrikethrough = false; // status of strikethrough for previous glyph. + std::map mapStrikethroughChunkIdWithProperties; // mapping strikethroughChunkId with StrikethroughStyleProperties to get properties of strikethrough chunk + StrikethroughStyleProperties preStrikethroughProperties = viewStrikethroughProperties; // the previous StrikethroughStyleProperties + + const Character* textBuffer = view.GetTextBuffer(); + float calculatedAdvance = 0.f; + const Vector& glyphToCharacterMap = view.GetGlyphsToCharacters(); + const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); + + //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text + if(hyphenIndices) + { + while((hyphenIndex < hyphensCount) && (hyphenIndices[hyphenIndex] < startIndexOfGlyphs || + (hyphenIndices[hyphenIndex] > firstMiddleIndexOfElidedGlyphs && hyphenIndices[hyphenIndex] < secondMiddleIndexOfElidedGlyphs))) + { + ++hyphenIndex; + } + } + //To keep the last fontMetrics of lastDecorativeLinesFontId + FontId lastDecorativeLinesFontId = 0; // DecorativeLines like Undeline and Strikethrough + FontMetrics lastDecorativeLinesFontMetrics; + fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics); + + // Iteration on glyphs for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i) { - const GlyphInfo& glyph = *(glyphsBuffer + i); - const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns); - thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || isGlyphUnderlined; + GlyphInfo glyph; + bool addHyphen = ((hyphenIndex < hyphensCount) && hyphenIndices && ((i + startIndexOfGlyphs) == hyphenIndices[hyphenIndex])); + if(addHyphen && hyphens) + { + glyph = hyphens[hyphenIndex]; + i--; + } + else + { + glyph = *(glyphsBuffer + i); + } + + Vector::ConstIterator currentUnderlinedGlyphRunIt = underlineRuns.End(); + const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns, currentUnderlinedGlyphRunIt); + const UnderlineStyleProperties currentUnderlineProperties = GetCurrentUnderlineProperties(i, isGlyphUnderlined, underlineRuns, currentUnderlinedGlyphRunIt, viewUnderlineProperties); + float currentUnderlineHeight = currentUnderlineProperties.height; + thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || isGlyphUnderlined; + + Vector::ConstIterator currentStrikethroughGlyphRunIt = strikethroughRuns.End(); + const bool isGlyphStrikethrough = strikethroughEnabled || IsGlyphStrikethrough(i, strikethroughRuns, currentStrikethroughGlyphRunIt); + const StrikethroughStyleProperties currentStrikethroughProperties = GetCurrentStrikethroughProperties(i, isGlyphStrikethrough, strikethroughRuns, currentStrikethroughGlyphRunIt, viewStrikethroughProperties); + float currentStrikethroughHeight = currentStrikethroughProperties.height; + thereAreStrikethroughGlyphs = thereAreStrikethroughGlyphs || isGlyphStrikethrough; // No operation for white space if(glyph.width && glyph.height) { - // Are we still using the same fontId as previous - if(isGlyphUnderlined && (glyph.fontId != lastUnderlinedFontId)) + // Check and update decorative-lines informations + if(isGlyphUnderlined || isGlyphStrikethrough) { - // We need to fetch fresh font underline metrics - FontMetrics fontMetrics; - mFontClient.GetFontMetrics(glyph.fontId, fontMetrics); - currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition)); - const float descender = ceil(fabsf(fontMetrics.descender)); - - if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000) + bool isDecorativeLinesFontIdUpdated = false; + // Are we still using the same fontId as previous + if(glyph.fontId != lastDecorativeLinesFontId) { - currentUnderlineThickness = fontMetrics.underlineThickness; + // We need to fetch fresh font metrics + lastDecorativeLinesFontId = glyph.fontId; + isDecorativeLinesFontIdUpdated = true; + fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics); - // Ensure underline will be at least a pixel high - if(currentUnderlineThickness < ONE) - { - currentUnderlineThickness = ONE; - } - else + if(isGlyphStrikethrough || isGlyphUnderlined) { - currentUnderlineThickness = ceil(currentUnderlineThickness); + //The currentUnderlinePosition will be used for both Underline and/or Strikethrough + currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(lastDecorativeLinesFontMetrics); } } - // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font - if(currentUnderlinePosition > descender) + if(isGlyphUnderlined && (isDecorativeLinesFontIdUpdated || !(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties)))) { - currentUnderlinePosition = descender; + //If the Underline Height is changed then we need to recalculate height. + if(!(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties))) + { + maxUnderlineHeight = currentUnderlineHeight; + } + + CalcualteUnderlineHeight(lastDecorativeLinesFontMetrics, currentUnderlineHeight, maxUnderlineHeight); } - if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000) + if(isGlyphStrikethrough && (isDecorativeLinesFontIdUpdated || !(currentStrikethroughProperties.IsHeightEqualTo(preStrikethroughProperties)))) { - // Move offset down by one ( EFL behavior ) - currentUnderlinePosition = ONE; - } + //If the Strikethrough Height is changed then we need to recalculate height. + if(!(currentStrikethroughProperties.IsHeightEqualTo(preStrikethroughProperties))) + { + maxStrikethroughHeight = currentStrikethroughHeight; + } - lastUnderlinedFontId = glyph.fontId; - } // underline + CalcualteStrikethroughHeight(currentStrikethroughHeight, maxStrikethroughHeight); + } + } // decorative-lines AtlasGlyphManager::GlyphStyle style; style.isItalic = glyph.isItalicRequired; @@ -505,8 +618,17 @@ struct AtlasRenderer::Impl } // Move the origin (0,0) of the mesh to the center of the actor - const Vector2& temp = *(positionsBuffer + i); - const Vector2 position = Vector2(roundf(temp.x), temp.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues. + Vector2 position = *(positionsBuffer + i); + + if(addHyphen) + { + GlyphInfo tempInfo = *(glyphsBuffer + i); + calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + i))), characterSpacing, tempInfo.advance); + position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyph.xBearing; + position.y += tempInfo.yBearing - glyph.yBearing; + } + + position = Vector2(roundf(position.x), position.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues. if(0u != slot.mImageId) // invalid slot id, glyph has failed to be added to atlas { @@ -522,6 +644,18 @@ struct AtlasRenderer::Impl const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndicesBuffer + i); const Vector4& color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + colorIndex - 1u); + //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case) + // Examples: "Hello World Hello World", "World Hello World", " World Hello World" + if((!isPreUnderlined && isGlyphUnderlined) || (isGlyphUnderlined && (preUnderlineProperties != currentUnderlineProperties))) + { + underlineChunkId++; + mapUnderlineChunkIdWithProperties.insert(std::pair(underlineChunkId, currentUnderlineProperties)); + } + + //Keep status of underlined for previous glyph to check consecutive indices + isPreUnderlined = isGlyphUnderlined; + preUnderlineProperties = currentUnderlineProperties; + GenerateMesh(glyph, positionPlusOutlineOffset, color, @@ -529,10 +663,43 @@ struct AtlasRenderer::Impl slot, isGlyphUnderlined, currentUnderlinePosition, - currentUnderlineThickness, + maxUnderlineHeight, meshContainer, newTextCache, - extents); + extents, + underlineChunkId, + false, + 0u); + + if(isGlyphStrikethrough) + { + //The new strikethrough chunk. Add new id if they are not consecutive indices (this is for Markup case) + // Examples: "Hello World Hello World", "World Hello World", " World Hello World" + if((!isPreStrikethrough) || (preStrikethroughProperties != currentStrikethroughProperties)) + { + strikethroughChunkId++; + mapStrikethroughChunkIdWithProperties.insert(std::pair(strikethroughChunkId, currentStrikethroughProperties)); + } + + GenerateMesh(glyph, + positionPlusOutlineOffset, + color, + NO_OUTLINE, + slot, + isGlyphStrikethrough, + 0.0f, + maxStrikethroughHeight, + meshContainer, + newTextCache, + strikethroughExtents, + 0u, + true, + strikethroughChunkId); + } + + //Keep status of Strikethrough for previous glyph to check consecutive indices + isPreStrikethrough = isGlyphStrikethrough; + preStrikethroughProperties = currentStrikethroughProperties; lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId. } @@ -546,12 +713,20 @@ struct AtlasRenderer::Impl slotOutline, false, currentUnderlinePosition, - currentUnderlineThickness, + maxUnderlineHeight, meshContainerOutline, newTextCache, - extents); + extents, + 0u, + false, + 0u); } } + + if(addHyphen) + { + hyphenIndex++; + } } // glyphs // Now remove references for the old text @@ -561,7 +736,13 @@ struct AtlasRenderer::Impl if(thereAreUnderlinedGlyphs) { // Check to see if any of the text needs an underline - GenerateUnderlines(meshContainer, extents, underlineColor); + GenerateUnderlines(meshContainer, extents, viewUnderlineProperties, mapUnderlineChunkIdWithProperties); + } + + if(thereAreStrikethroughGlyphs) + { + // Check to see if any of the text needs a strikethrough + GenerateStrikethrough(meshContainer, strikethroughExtents, viewStrikethroughProperties, mapStrikethroughChunkIdWithProperties); } // For each MeshData object, create a mesh actor and add to the renderable actor @@ -698,10 +879,13 @@ struct AtlasRenderer::Impl AtlasManager::Mesh2D& newMesh, Vector& extents, float baseLine, - bool underlineGlyph, + bool decorationlineGlyph, float underlinePosition, - float underlineThickness, - AtlasManager::AtlasSlot& slot) + float lineThickness, + AtlasManager::AtlasSlot& slot, + uint32_t underlineChunkId, + float strikethroughPosition, + uint32_t strikethroughChunkId) { if(slot.mImageId) { @@ -720,7 +904,7 @@ struct AtlasRenderer::Impl // Append the mesh to the existing mesh and adjust any extents Toolkit::Internal::AtlasMeshFactory::AppendMesh(mIt->mMesh, newMesh); - if(underlineGlyph) + if(decorationlineGlyph) { AdjustExtents(extents, meshContainer, @@ -729,7 +913,10 @@ struct AtlasRenderer::Impl right, baseLine, underlinePosition, - underlineThickness); + lineThickness, + underlineChunkId, + strikethroughPosition, + strikethroughChunkId); } return; @@ -742,7 +929,7 @@ struct AtlasRenderer::Impl meshRecord.mMesh = newMesh; meshContainer.push_back(meshRecord); - if(underlineGlyph) + if(decorationlineGlyph) { // Adjust extents for this new meshrecord AdjustExtents(extents, @@ -752,7 +939,10 @@ struct AtlasRenderer::Impl right, baseLine, underlinePosition, - underlineThickness); + lineThickness, + underlineChunkId, + strikethroughPosition, + strikethroughChunkId); } } } @@ -764,7 +954,10 @@ struct AtlasRenderer::Impl float right, float baseLine, float underlinePosition, - float underlineThickness) + float lineThickness, + uint32_t underlineChunkId, + float strikethroughPosition, + uint32_t strikethroughChunkId) { bool foundExtent = false; for(Vector::Iterator eIt = extents.Begin(), @@ -772,7 +965,7 @@ struct AtlasRenderer::Impl eIt != eEndIt; ++eIt) { - if(Equals(baseLine, eIt->mBaseLine)) + if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId && strikethroughChunkId == eIt->mStrikethroughChunkId) { foundExtent = true; if(left < eIt->mLeft) @@ -788,21 +981,24 @@ struct AtlasRenderer::Impl { eIt->mUnderlinePosition = underlinePosition; } - if(underlineThickness > eIt->mUnderlineThickness) + if(lineThickness > eIt->mLineThickness) { - eIt->mUnderlineThickness = underlineThickness; + eIt->mLineThickness = lineThickness; } } } if(!foundExtent) { Extent extent; - extent.mLeft = left; - extent.mRight = right; - extent.mBaseLine = baseLine; - extent.mUnderlinePosition = underlinePosition; - extent.mUnderlineThickness = underlineThickness; - extent.mMeshRecordIndex = index; + extent.mLeft = left; + extent.mRight = right; + extent.mBaseLine = baseLine; + extent.mUnderlinePosition = underlinePosition; + extent.mMeshRecordIndex = index; + extent.mUnderlineChunkId = underlineChunkId; + extent.mLineThickness = lineThickness; + extent.mStrikethroughPosition = strikethroughPosition; + extent.mStrikethroughChunkId = strikethroughChunkId; extents.PushBack(extent); } } @@ -845,9 +1041,191 @@ struct AtlasRenderer::Impl } } - void GenerateUnderlines(std::vector& meshRecords, - Vector& extents, - const Vector4& underlineColor) + void GenerateUnderlines(std::vector& meshRecords, + Vector& extents, + const UnderlineStyleProperties& viewUnderlineProperties, + const std::map& mapUnderlineChunkIdWithProperties) + { + AtlasManager::Mesh2D newMesh; + unsigned short faceIndex = 0; + + for(Vector::ConstIterator eIt = extents.Begin(), + eEndIt = extents.End(); + eIt != eEndIt; + ++eIt) + { + AtlasManager::Vertex2D vert; + uint32_t index = eIt->mMeshRecordIndex; + Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId); + + auto pairUnderlineChunkIdWithProperties = mapUnderlineChunkIdWithProperties.find(eIt->mUnderlineChunkId); + + const UnderlineStyleProperties underlineProperties = (pairUnderlineChunkIdWithProperties == mapUnderlineChunkIdWithProperties.end()) + ? viewUnderlineProperties + : pairUnderlineChunkIdWithProperties->second; + + const Vector4& underlineColor = underlineProperties.colorDefined ? underlineProperties.color : viewUnderlineProperties.color; + const Text::Underline::Type& underlineType = underlineProperties.typeDefined ? underlineProperties.type : viewUnderlineProperties.type; + const float& dashedUnderlineGap = underlineProperties.dashGapDefined ? underlineProperties.dashGap : viewUnderlineProperties.dashGap; + const float& dashedUnderlineWidth = underlineProperties.dashWidthDefined ? underlineProperties.dashWidth : viewUnderlineProperties.dashWidth; + + // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas ) + float u = HALF / uv.x; + float v = HALF / uv.y; + float thickness = eIt->mLineThickness; + float ShiftLineBy = (underlineType == Text::Underline::Type::DOUBLE) ? (thickness * ONE_AND_A_HALF) : (thickness * HALF); + float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - ShiftLineBy; + float tlx = eIt->mLeft; + float brx = eIt->mRight; + + if(underlineType == Text::Underline::Type::DASHED) + { + float dashTlx = tlx; + float dashBrx = tlx; + + while((dashTlx >= tlx) && (dashTlx < brx) && ((dashTlx + dashedUnderlineWidth) <= brx)) + { + dashBrx = dashTlx + dashedUnderlineWidth; + + //The top left edge of the underline + vert.mPosition.x = dashTlx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = ZERO; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The top right edge of the underline + vert.mPosition.x = dashBrx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The bottom left edge of the underline + vert.mPosition.x = dashTlx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = v; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The bottom right edge of the underline + vert.mPosition.x = dashBrx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + dashTlx = dashBrx + dashedUnderlineGap; // The next dash will start at the right of the current dash plus the gap + + // Six indices in counter clockwise winding + newMesh.mIndices.PushBack(faceIndex + 1u); + newMesh.mIndices.PushBack(faceIndex); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 3u); + newMesh.mIndices.PushBack(faceIndex + 1u); + + faceIndex += 4; + + Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh); + } + } + else + { + // It's either SOLID or DOUBLE so we need to generate the first solid underline anyway. + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = ZERO; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + vert.mPosition.x = brx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = v; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + vert.mPosition.x = brx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + // Six indices in counter clockwise winding + newMesh.mIndices.PushBack(faceIndex + 1u); + newMesh.mIndices.PushBack(faceIndex); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 3u); + newMesh.mIndices.PushBack(faceIndex + 1u); + faceIndex += 4; + + Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh); + + if(underlineType == Text::Underline::Type::DOUBLE) + { + baseLine += 2 * thickness; + + //The top left edge of the underline + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine; // Vertical start of the second underline + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = ZERO; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The top right edge of the underline + vert.mPosition.x = brx; + vert.mPosition.y = baseLine; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The bottom left edge of the underline + vert.mPosition.x = tlx; + vert.mPosition.y = baseLine + thickness; // Vertical End of the second underline + vert.mTexCoords.x = ZERO; + vert.mTexCoords.y = v; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + //The bottom right edge of the underline + vert.mPosition.x = brx; + vert.mPosition.y = baseLine + thickness; + vert.mTexCoords.x = u; + vert.mColor = underlineColor; + newMesh.mVertices.PushBack(vert); + + // Six indices in counter clockwise winding + newMesh.mIndices.PushBack(faceIndex + 1u); + newMesh.mIndices.PushBack(faceIndex); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 2u); + newMesh.mIndices.PushBack(faceIndex + 3u); + newMesh.mIndices.PushBack(faceIndex + 1u); + + faceIndex += 4; + + Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh); + } + } + } + } + + void GenerateStrikethrough(std::vector& meshRecords, + Vector& extents, + const StrikethroughStyleProperties& viewStrikethroughProperties, + const std::map& mapStrikethroughChunkIdWithProperties) { AtlasManager::Mesh2D newMesh; unsigned short faceIndex = 0; @@ -860,38 +1238,46 @@ struct AtlasRenderer::Impl uint32_t index = eIt->mMeshRecordIndex; Vector2 uv = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId); + auto pairStrikethroughChunkIdWithProperties = mapStrikethroughChunkIdWithProperties.find(eIt->mStrikethroughChunkId); + + const StrikethroughStyleProperties strikethroughProperties = (pairStrikethroughChunkIdWithProperties == mapStrikethroughChunkIdWithProperties.end()) + ? viewStrikethroughProperties + : pairStrikethroughChunkIdWithProperties->second; + + const Vector4& strikethroughColor = strikethroughProperties.colorDefined ? strikethroughProperties.color : viewStrikethroughProperties.color; + // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas ) - float u = HALF / uv.x; - float v = HALF / uv.y; - float thickness = eIt->mUnderlineThickness; - float baseLine = eIt->mBaseLine + eIt->mUnderlinePosition - (thickness * HALF); - float tlx = eIt->mLeft; - float brx = eIt->mRight; + float u = HALF / uv.x; + float v = HALF / uv.y; + float thickness = eIt->mLineThickness; + float tlx = eIt->mLeft; + float brx = eIt->mRight; + float strikethroughPosition = eIt->mStrikethroughPosition; vert.mPosition.x = tlx; - vert.mPosition.y = baseLine; + vert.mPosition.y = strikethroughPosition; vert.mTexCoords.x = ZERO; vert.mTexCoords.y = ZERO; - vert.mColor = underlineColor; + vert.mColor = strikethroughColor; newMesh.mVertices.PushBack(vert); vert.mPosition.x = brx; - vert.mPosition.y = baseLine; + vert.mPosition.y = strikethroughPosition; vert.mTexCoords.x = u; - vert.mColor = underlineColor; + vert.mColor = strikethroughColor; newMesh.mVertices.PushBack(vert); vert.mPosition.x = tlx; - vert.mPosition.y = baseLine + thickness; + vert.mPosition.y = strikethroughPosition + thickness; vert.mTexCoords.x = ZERO; vert.mTexCoords.y = v; - vert.mColor = underlineColor; + vert.mColor = strikethroughColor; newMesh.mVertices.PushBack(vert); vert.mPosition.x = brx; - vert.mPosition.y = baseLine + thickness; + vert.mPosition.y = strikethroughPosition + thickness; vert.mTexCoords.x = u; - vert.mColor = underlineColor; + vert.mColor = strikethroughColor; newMesh.mVertices.PushBack(vert); // Six indices in counter clockwise winding