2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/shaper.h>
23 #include <dali/devel-api/text-abstraction/font-client.h>
24 #include <dali/devel-api/text-abstraction/shaping.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/integration-api/trace.h>
34 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false);
36 CharacterIndex min(CharacterIndex index0,
37 CharacterIndex index1)
39 return (index0 < index1) ? index0 : index1;
42 #if defined(TRACE_ENABLED)
43 uint32_t GetMilliSeconds()
45 // Get the time of a monotonic clock since its epoch.
46 auto epoch = std::chrono::steady_clock::now().time_since_epoch();
48 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
50 return static_cast<uint32_t>(duration.count());
54 void ShapeText(const Vector<Character>& text,
55 const Vector<LineBreakInfo>& lineBreakInfo,
56 const Vector<ScriptRun>& scripts,
57 const Vector<FontRun>& fonts,
58 CharacterIndex startCharacterIndex,
59 GlyphIndex startGlyphIndex,
60 Length numberOfCharacters,
61 Vector<GlyphInfo>& glyphs,
62 Vector<CharacterIndex>& glyphToCharacterMap,
63 Vector<Length>& charactersPerGlyph,
64 Vector<GlyphIndex>& newParagraphGlyphs)
66 if(0u == numberOfCharacters)
68 // Nothing to do if there are no characters.
72 DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_SHAPE_TEXT");
74 #if defined(TRACE_ENABLED)
75 uint32_t sumPre = 0, sumShape = 0, sumPost = 0;
77 uint32_t logThreshold = TextAbstraction::FontClient::GetPerformanceLogThresholdTime();
78 bool logEnabled = TextAbstraction::FontClient::IsPerformanceLogEnabled();
82 const Length numberOfFontRuns = fonts.Count();
83 const Length numberOfScriptRuns = scripts.Count();
84 const Length totalNumberOfCharacters = text.Count();
87 DALI_ASSERT_DEBUG((0u != numberOfFontRuns) &&
88 (totalNumberOfCharacters == fonts[numberOfFontRuns - 1u].characterRun.characterIndex + fonts[numberOfFontRuns - 1u].characterRun.numberOfCharacters) &&
89 "Toolkit::Text::ShapeText. All characters must have a font set.");
91 DALI_ASSERT_DEBUG((0u != numberOfScriptRuns) &&
92 (totalNumberOfCharacters == scripts[numberOfScriptRuns - 1u].characterRun.characterIndex + scripts[numberOfScriptRuns - 1u].characterRun.numberOfCharacters) &&
93 "Toolkit::Text::ShapeText. All characters must have a script set.");
95 // The text needs to be split in chunks of consecutive characters.
96 // Each chunk must contain characters with the same font id and script set.
97 // A chunk of consecutive characters must not contain a LINE_MUST_BREAK, if there is one a new chunk has to be created.
99 TextAbstraction::Shaping shaping = TextAbstraction::Shaping::Get();
101 // To shape the text a font and an script is needed.
103 // Get the font run containing the startCharacterIndex character.
104 Vector<FontRun>::ConstIterator fontRunIt = fonts.Begin();
105 for(Vector<FontRun>::ConstIterator endIt = fonts.End(); fontRunIt < endIt; ++fontRunIt)
107 const FontRun& run = *fontRunIt;
108 if(startCharacterIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
115 // Get the script run containing the startCharacterIndex character.
116 Vector<ScriptRun>::ConstIterator scriptRunIt = scripts.Begin();
117 for(Vector<ScriptRun>::ConstIterator endIt = scripts.End(); scriptRunIt < endIt; ++scriptRunIt)
119 const ScriptRun& run = *scriptRunIt;
120 if(startCharacterIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
127 // Index to the the next one to be shaped. Is pointing the character after the last one it was shaped.
128 CharacterIndex previousIndex = 0u;
130 // The current font id and script used to shape the text.
131 FontId currentFontId = 0u;
132 Script currentScript = TextAbstraction::UNKNOWN;
134 // Reserve some space to allocate the glyphs and the glyph to character map.
135 // There is no way to know the number of glyphs before shaping the text.
136 // To avoid reallocations it's reserved space for a slightly biger number of glyphs than the number of characters.
139 glyphInfo.isItalicRequired = false;
140 glyphInfo.isBoldRequired = false;
142 const Length currentNumberOfGlyphs = glyphs.Count();
143 const Length numberOfGlyphsReserved = static_cast<Length>(numberOfCharacters * 1.3f);
144 glyphs.Reserve(currentNumberOfGlyphs + numberOfGlyphsReserved);
145 glyphToCharacterMap.Reserve(currentNumberOfGlyphs + numberOfGlyphsReserved);
147 // The actual number of glyphs.
148 Length totalNumberOfGlyphs = currentNumberOfGlyphs;
149 // The number of new glyphs.
150 Length numberOfNewGlyphs = 0u;
152 const Character* const textBuffer = text.Begin();
153 const LineBreakInfo* const lineBreakInfoBuffer = lineBreakInfo.Begin();
154 CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
156 Length glyphIndex = startGlyphIndex;
158 // Traverse the characters and shape the text.
159 const CharacterIndex lastCharacter = startCharacterIndex + numberOfCharacters;
160 for(previousIndex = startCharacterIndex; previousIndex < lastCharacter;)
162 #if defined(TRACE_ENABLED)
163 uint32_t timeStamps[4];
164 uint32_t timeStampIndex = 0;
168 timeStamps[timeStampIndex++] = GetMilliSeconds();
172 // Get the font id and the script.
173 const FontRun& fontRun = *fontRunIt;
174 const ScriptRun& scriptRun = *scriptRunIt;
176 currentFontId = fontRun.fontId;
177 currentScript = scriptRun.script;
178 const bool isItalicRequired = fontRun.isItalicRequired;
179 const bool isBoldRequired = fontRun.isBoldRequired;
181 // Get the min index to the last character of both runs.
182 CharacterIndex currentIndex = min(fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters,
183 scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters);
185 // Check if there is a line must break.
186 bool mustBreak = false;
188 // Check if the current index is a new paragraph character.
189 // A new paragraph character is going to be shaped in order to not to mess the conversion tables.
190 // However, the metrics need to be changed in order to not to draw a square.
191 bool isNewParagraph = false;
193 for(CharacterIndex index = previousIndex; index < currentIndex; ++index)
195 mustBreak = TextAbstraction::LINE_MUST_BREAK == *(lineBreakInfoBuffer + index);
198 isNewParagraph = TextAbstraction::IsNewParagraph(*(textBuffer + index));
199 currentIndex = index + 1u;
204 #if defined(TRACE_ENABLED)
207 timeStamps[timeStampIndex++] = GetMilliSeconds();
211 // Shape the text for the current chunk.
212 const Length numberOfGlyphs = shaping.Shape(textBuffer + previousIndex,
213 (currentIndex - previousIndex), // The number of characters to shape.
217 #if defined(TRACE_ENABLED)
220 timeStamps[timeStampIndex++] = GetMilliSeconds();
224 // Retrieve the glyphs and the glyph to character conversion map.
225 Vector<GlyphInfo> tmpGlyphs;
226 Vector<CharacterIndex> tmpGlyphToCharacterMap;
229 glyphInfo.isItalicRequired = isItalicRequired;
230 glyphInfo.isBoldRequired = isBoldRequired;
232 tmpGlyphs.Resize(numberOfGlyphs, glyphInfo);
233 tmpGlyphToCharacterMap.Resize(numberOfGlyphs);
234 shaping.GetGlyphs(tmpGlyphs.Begin(),
235 tmpGlyphToCharacterMap.Begin());
237 // Update the new indices of the glyph to character map.
238 if(0u != totalNumberOfGlyphs)
240 for(Vector<CharacterIndex>::Iterator it = tmpGlyphToCharacterMap.Begin(),
241 endIt = tmpGlyphToCharacterMap.End();
245 *it += previousIndex;
249 totalNumberOfGlyphs += numberOfGlyphs;
250 numberOfNewGlyphs += numberOfGlyphs;
252 glyphs.Insert(glyphs.Begin() + glyphIndex, tmpGlyphs.Begin(), tmpGlyphs.End());
253 glyphToCharacterMap.Insert(glyphToCharacterMap.Begin() + glyphIndex, tmpGlyphToCharacterMap.Begin(), tmpGlyphToCharacterMap.End());
254 glyphIndex += numberOfGlyphs;
256 // Set the buffer pointers again.
257 glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
261 // Add the index of the new paragraph glyph to a vector.
262 // Their metrics will be updated in a following step.
263 newParagraphGlyphs.PushBack(glyphIndex - 1u);
266 // Update the iterators to get the next font or script run.
267 if(currentIndex == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters)
271 if(currentIndex == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters)
276 // Update the previous index.
277 previousIndex = currentIndex;
279 #if defined(TRACE_ENABLED)
282 timeStamps[timeStampIndex++] = GetMilliSeconds();
283 sumPre += timeStamps[1] - timeStamps[0];
284 sumShape += timeStamps[2] - timeStamps[1];
285 sumPost += timeStamps[3] - timeStamps[2];
291 for(Length index = startGlyphIndex + numberOfNewGlyphs; index < totalNumberOfGlyphs; ++index)
293 CharacterIndex& characterIndex = *(glyphToCharacterMapBuffer + index);
294 characterIndex += numberOfCharacters;
297 // Add the number of characters per glyph.
298 charactersPerGlyph.Reserve(totalNumberOfGlyphs);
299 Length* charactersPerGlyphBuffer = charactersPerGlyph.Begin();
301 const GlyphIndex lastGlyph = startGlyphIndex + numberOfNewGlyphs;
302 previousIndex = startCharacterIndex;
303 for(Length index = startGlyphIndex + 1u; index < lastGlyph; ++index)
305 const CharacterIndex characterIndex = *(glyphToCharacterMapBuffer + index);
307 charactersPerGlyph.Insert(charactersPerGlyphBuffer + index - 1u, characterIndex - previousIndex);
309 previousIndex = characterIndex;
311 charactersPerGlyph.Insert(charactersPerGlyphBuffer + lastGlyph - 1u, numberOfCharacters + startCharacterIndex - previousIndex);
313 // Resize the vectors to set the right number of items.
314 glyphs.Resize(totalNumberOfGlyphs);
315 glyphToCharacterMap.Resize(totalNumberOfGlyphs);
317 #if defined(TRACE_ENABLED)
320 if(sumPre + sumShape + sumPost > logThreshold)
322 DALI_LOG_DEBUG_INFO("DALI_TEXT_SHAPE_TEXT updated:%u/%u, pre:%u ms, shape:%u ms, post:%u ms\n", numberOfNewGlyphs, totalNumberOfGlyphs, sumPre, sumShape, sumPost);
330 } // namespace Toolkit