[dali_2.3.21] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / shaper.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/shaper.h>
20
21 // EXTERNAL INCLUDES
22 #include <chrono>
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>
27
28 namespace Dali
29 {
30 namespace Toolkit
31 {
32 namespace Text
33 {
34 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false);
35
36 CharacterIndex min(CharacterIndex index0,
37                    CharacterIndex index1)
38 {
39   return (index0 < index1) ? index0 : index1;
40 }
41
42 #if defined(TRACE_ENABLED)
43 uint32_t GetMilliSeconds()
44 {
45   // Get the time of a monotonic clock since its epoch.
46   auto epoch = std::chrono::steady_clock::now().time_since_epoch();
47
48   auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
49
50   return static_cast<uint32_t>(duration.count());
51 }
52 #endif
53
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)
65 {
66   if(0u == numberOfCharacters)
67   {
68     // Nothing to do if there are no characters.
69     return;
70   }
71
72   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_SHAPE_TEXT");
73
74 #if defined(TRACE_ENABLED)
75   uint32_t sumPre = 0, sumShape = 0, sumPost = 0;
76
77   uint32_t logThreshold = TextAbstraction::FontClient::GetPerformanceLogThresholdTime();
78   bool     logEnabled   = TextAbstraction::FontClient::IsPerformanceLogEnabled();
79 #endif
80
81 #ifdef DEBUG_ENABLED
82   const Length numberOfFontRuns        = fonts.Count();
83   const Length numberOfScriptRuns      = scripts.Count();
84   const Length totalNumberOfCharacters = text.Count();
85 #endif
86
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.");
90
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.");
94
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.
98
99   TextAbstraction::Shaping shaping = TextAbstraction::Shaping::Get();
100
101   // To shape the text a font and an script is needed.
102
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)
106   {
107     const FontRun& run = *fontRunIt;
108     if(startCharacterIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
109     {
110       // Found.
111       break;
112     }
113   }
114
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)
118   {
119     const ScriptRun& run = *scriptRunIt;
120     if(startCharacterIndex < run.characterRun.characterIndex + run.characterRun.numberOfCharacters)
121     {
122       // Found.
123       break;
124     }
125   }
126
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;
129
130   // The current font id and script used to shape the text.
131   FontId currentFontId = 0u;
132   Script currentScript = TextAbstraction::UNKNOWN;
133
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.
137
138   GlyphInfo glyphInfo;
139   glyphInfo.isItalicRequired = false;
140   glyphInfo.isBoldRequired   = false;
141
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);
146
147   // The actual number of glyphs.
148   Length totalNumberOfGlyphs = currentNumberOfGlyphs;
149   // The number of new glyphs.
150   Length numberOfNewGlyphs = 0u;
151
152   const Character* const     textBuffer                = text.Begin();
153   const LineBreakInfo* const lineBreakInfoBuffer       = lineBreakInfo.Begin();
154   CharacterIndex*            glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
155
156   Length glyphIndex = startGlyphIndex;
157
158   // Traverse the characters and shape the text.
159   const CharacterIndex lastCharacter = startCharacterIndex + numberOfCharacters;
160   for(previousIndex = startCharacterIndex; previousIndex < lastCharacter;)
161   {
162 #if defined(TRACE_ENABLED)
163     uint32_t timeStamps[4];
164     uint32_t timeStampIndex = 0;
165
166     if(logEnabled)
167     {
168       timeStamps[timeStampIndex++] = GetMilliSeconds();
169     }
170 #endif
171
172     // Get the font id and the script.
173     const FontRun&   fontRun   = *fontRunIt;
174     const ScriptRun& scriptRun = *scriptRunIt;
175
176     currentFontId               = fontRun.fontId;
177     currentScript               = scriptRun.script;
178     const bool isItalicRequired = fontRun.isItalicRequired;
179     const bool isBoldRequired   = fontRun.isBoldRequired;
180
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);
184
185     // Check if there is a line must break.
186     bool mustBreak = false;
187
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;
192
193     for(CharacterIndex index = previousIndex; index < currentIndex; ++index)
194     {
195       mustBreak = TextAbstraction::LINE_MUST_BREAK == *(lineBreakInfoBuffer + index);
196       if(mustBreak)
197       {
198         isNewParagraph = TextAbstraction::IsNewParagraph(*(textBuffer + index));
199         currentIndex   = index + 1u;
200         break;
201       }
202     }
203
204 #if defined(TRACE_ENABLED)
205     if(logEnabled)
206     {
207       timeStamps[timeStampIndex++] = GetMilliSeconds();
208     }
209 #endif
210
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.
214                                                 currentFontId,
215                                                 currentScript);
216
217 #if defined(TRACE_ENABLED)
218     if(logEnabled)
219     {
220       timeStamps[timeStampIndex++] = GetMilliSeconds();
221     }
222 #endif
223
224     // Retrieve the glyphs and the glyph to character conversion map.
225     Vector<GlyphInfo>      tmpGlyphs;
226     Vector<CharacterIndex> tmpGlyphToCharacterMap;
227
228     GlyphInfo glyphInfo;
229     glyphInfo.isItalicRequired = isItalicRequired;
230     glyphInfo.isBoldRequired   = isBoldRequired;
231
232     tmpGlyphs.Resize(numberOfGlyphs, glyphInfo);
233     tmpGlyphToCharacterMap.Resize(numberOfGlyphs);
234     shaping.GetGlyphs(tmpGlyphs.Begin(),
235                       tmpGlyphToCharacterMap.Begin());
236
237     // Update the new indices of the glyph to character map.
238     if(0u != totalNumberOfGlyphs)
239     {
240       for(Vector<CharacterIndex>::Iterator it    = tmpGlyphToCharacterMap.Begin(),
241                                            endIt = tmpGlyphToCharacterMap.End();
242           it != endIt;
243           ++it)
244       {
245         *it += previousIndex;
246       }
247     }
248
249     totalNumberOfGlyphs += numberOfGlyphs;
250     numberOfNewGlyphs += numberOfGlyphs;
251
252     glyphs.Insert(glyphs.Begin() + glyphIndex, tmpGlyphs.Begin(), tmpGlyphs.End());
253     glyphToCharacterMap.Insert(glyphToCharacterMap.Begin() + glyphIndex, tmpGlyphToCharacterMap.Begin(), tmpGlyphToCharacterMap.End());
254     glyphIndex += numberOfGlyphs;
255
256     // Set the buffer pointers again.
257     glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
258
259     if(isNewParagraph)
260     {
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);
264     }
265
266     // Update the iterators to get the next font or script run.
267     if(currentIndex == fontRun.characterRun.characterIndex + fontRun.characterRun.numberOfCharacters)
268     {
269       ++fontRunIt;
270     }
271     if(currentIndex == scriptRun.characterRun.characterIndex + scriptRun.characterRun.numberOfCharacters)
272     {
273       ++scriptRunIt;
274     }
275
276     // Update the previous index.
277     previousIndex = currentIndex;
278
279 #if defined(TRACE_ENABLED)
280     if(logEnabled)
281     {
282       timeStamps[timeStampIndex++] = GetMilliSeconds();
283       sumPre   += timeStamps[1] - timeStamps[0];
284       sumShape += timeStamps[2] - timeStamps[1];
285       sumPost  += timeStamps[3] - timeStamps[2];
286     }
287 #endif
288   }
289
290   // Update indices.
291   for(Length index = startGlyphIndex + numberOfNewGlyphs; index < totalNumberOfGlyphs; ++index)
292   {
293     CharacterIndex& characterIndex = *(glyphToCharacterMapBuffer + index);
294     characterIndex += numberOfCharacters;
295   }
296
297   // Add the number of characters per glyph.
298   charactersPerGlyph.Reserve(totalNumberOfGlyphs);
299   Length* charactersPerGlyphBuffer = charactersPerGlyph.Begin();
300
301   const GlyphIndex lastGlyph = startGlyphIndex + numberOfNewGlyphs;
302   previousIndex              = startCharacterIndex;
303   for(Length index = startGlyphIndex + 1u; index < lastGlyph; ++index)
304   {
305     const CharacterIndex characterIndex = *(glyphToCharacterMapBuffer + index);
306
307     charactersPerGlyph.Insert(charactersPerGlyphBuffer + index - 1u, characterIndex - previousIndex);
308
309     previousIndex = characterIndex;
310   }
311   charactersPerGlyph.Insert(charactersPerGlyphBuffer + lastGlyph - 1u, numberOfCharacters + startCharacterIndex - previousIndex);
312
313   // Resize the vectors to set the right number of items.
314   glyphs.Resize(totalNumberOfGlyphs);
315   glyphToCharacterMap.Resize(totalNumberOfGlyphs);
316
317 #if defined(TRACE_ENABLED)
318   if(logEnabled)
319   {
320     if(sumPre + sumShape + sumPost > logThreshold)
321     {
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);
323     }
324   }
325 #endif
326 }
327
328 } // namespace Text
329
330 } // namespace Toolkit
331
332 } // namespace Dali