• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Google LLC.
2 
3 #include <stack>
4 #include "experimental/sktext/include/Processor.h"
5 #include "experimental/sktext/src/Formatter.h"
6 #include "experimental/sktext/src/Shaper.h"
7 #include "experimental/sktext/src/Wrapper.h"
8 
9 namespace skia {
10 namespace text {
11 
12 // The result of shaping is a set of Runs placed on one endless line
13 // It has all the glyph information
shape(TextFontStyle fontStyle,SkTArray<FontBlock,true> fontBlocks)14 bool Processor::shape(TextFontStyle fontStyle, SkTArray<FontBlock, true> fontBlocks) {
15 
16     if (fUnicode == nullptr) {
17         return false;
18     }
19 
20     fFontBlocks = std::move(fontBlocks);
21 
22     Shaper shaper(this, fontStyle);
23     if (!shaper.process()) {
24         return false;
25     }
26 
27     this->markGlyphs();
28 
29     return true;
30 }
31 
32 // TODO: we can wrap to any shape, not just a rectangle
33 // The result of wrapping is a set of Lines that fit the required sizes and
34 // contain all the glyph information
wrap(SkScalar width,SkScalar height)35 bool Processor::wrap(SkScalar width, SkScalar height) {
36 
37     Wrapper wrapper(this, width, height);
38     if (!wrapper.process()) {
39         return false;
40     }
41     return true;
42 }
43 
44 // The result of formatting is a possible shift of glyphs as the format type requires
format(TextFormatStyle formatStyle)45 bool Processor::format(TextFormatStyle formatStyle) {
46 
47     Formatter formatter(this, formatStyle);
48     if (!formatter.process()) {
49         return false;
50     }
51     return true;
52 }
53 
54 // Once the text is decorated you can iterate it by segments (intersect of run, decor block and line)
decorate(SkTArray<DecorBlock,true> decorBlocks)55 bool Processor::decorate(SkTArray<DecorBlock, true> decorBlocks) {
56 
57     this->iterateByVisualOrder(decorBlocks,
58        [&](SkSize offset, SkScalar baseline, const TextRun* run, TextRange textRange, GlyphRange glyphRange, const DecorBlock& block) {
59         SkTextBlobBuilder builder;
60         const auto& blobBuffer = builder.allocRunPos(run->fFont, SkToInt(glyphRange.width()));
61         sk_careful_memcpy(blobBuffer.glyphs, run->fGlyphs.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkGlyphID));
62         sk_careful_memcpy(blobBuffer.points(), run->fPositions.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkPoint));
63 
64         offset.fHeight += baseline;
65         fTextOutputs.emplace_back(builder.make(), *block.fForegroundColor, offset);
66     });
67 
68     return true;
69 }
70 
71 // All at once
drawText(const char * text,SkCanvas * canvas,SkScalar x,SkScalar y)72 bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar x, SkScalar y) {
73 
74     return drawText(text, canvas, TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), x, y);
75 }
76 
drawText(const char * text,SkCanvas * canvas,SkScalar width)77 bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar width) {
78     return drawText(text, canvas,
79                     TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(),
80                     SkSize::Make(width, SK_ScalarInfinity), 0, 0);
81 }
82 
drawText(const char * text,SkCanvas * canvas,TextFormatStyle textFormat,SkColor foreground,SkColor background,const SkString & fontFamily,SkScalar fontSize,SkFontStyle fontStyle,SkScalar x,SkScalar y)83 bool Processor::drawText(const char* text, SkCanvas* canvas, TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle, SkScalar x, SkScalar y) {
84     return drawText(text, canvas, textFormat, foreground, background, fontFamily, fontSize, fontStyle, SkSize::Make(SK_ScalarInfinity, SK_ScalarInfinity), x, y);
85 }
86 
drawText(const char * text,SkCanvas * canvas,TextFormatStyle textFormat,SkColor foreground,SkColor background,const SkString & fontFamily,SkScalar fontSize,SkFontStyle fontStyle,SkSize reqSize,SkScalar x,SkScalar y)87 bool Processor::drawText(const char* text, SkCanvas* canvas,
88                          TextFormatStyle textFormat, SkColor foreground, SkColor background, const SkString& fontFamily, SkScalar fontSize, SkFontStyle fontStyle,
89                          SkSize reqSize, SkScalar x, SkScalar y) {
90 
91     SkString str(text);
92     TextRange textRange(0, str.size());
93     Processor processor(str);
94     if (!processor.computeCodeUnitProperties()) {
95         return false;
96     }
97     if (!processor.shape({ textFormat.fDefaultTextDirection, SkFontMgr::RefDefault()}, {{{ fontFamily, fontSize, fontStyle, textRange }}})) {
98         return false;
99     }
100     if (!processor.wrap(reqSize.fWidth, reqSize.fHeight)) {
101         return false;
102     }
103     if (!processor.format(textFormat)) {
104         return false;
105     }
106     SkTArray<DecorBlock, true> decor;
107     SkPaint backgroundPaint; backgroundPaint.setColor(background);
108     SkPaint foregroundPaint; foregroundPaint.setColor(foreground);
109     if (!processor.decorate({{{&foregroundPaint, &backgroundPaint, textRange}}})) {
110         return false;
111     }
112 
113     for (auto& output : processor.fTextOutputs) {
114         canvas->drawTextBlob(output.fTextBlob, x + output.fOffset.fWidth, y + output.fOffset.fHeight, output.fPaint);
115     }
116 
117     return true;
118 }
119 
120 // Also adjust the decoration block edges to cluster edges while we at it
121 // to avoid an enormous amount of complications
sortDecorBlocks(SkTArray<DecorBlock,true> & decorBlocks)122 void Processor::sortDecorBlocks(SkTArray<DecorBlock, true>& decorBlocks) {
123     // Soft the blocks
124     std::sort(decorBlocks.begin(), decorBlocks.end(),
125               [](const DecorBlock& a, const DecorBlock& b) {
126                 return a.fRange.fStart < b.fRange.fStart;
127               });
128     // Walk through the blocks using the default when missing
129     SkPaint* foreground = new SkPaint();
130     foreground->setColor(SK_ColorBLACK);
131     std::stack<DecorBlock> defaultBlocks;
132     defaultBlocks.emplace(foreground, nullptr, TextRange(0, fText.size()));
133     size_t start = 0;
134     for (auto& block : decorBlocks) {
135         this->adjustLeft(&block.fRange.fStart);
136         this->adjustLeft(&block.fRange.fEnd);
137         SkASSERT(start <= block.fRange.fStart);
138         if (start < block.fRange.fStart) {
139             auto defaultBlock = defaultBlocks.top();
140             decorBlocks.emplace_back(defaultBlock.fForegroundColor, defaultBlock.fBackgroundColor, Range(start, block.fRange.fStart));
141         }
142         start = block.fRange.fEnd;
143         while (!defaultBlocks.empty()) {
144             auto defaultBlock = defaultBlocks.top();
145             if (defaultBlock.fRange.fEnd <= block.fRange.fEnd) {
146                 defaultBlocks.pop();
147             }
148         }
149         defaultBlocks.push(block);
150     }
151     if (start < fText.size()) {
152         auto defaultBlock = defaultBlocks.top();
153         decorBlocks.emplace_back(defaultBlock.fForegroundColor, defaultBlock.fBackgroundColor, Range(start, fText.size()));
154     }
155 }
156 
computeCodeUnitProperties()157 bool Processor::computeCodeUnitProperties() {
158 
159     fCodeUnitProperties.push_back_n(fText.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
160 
161     fUnicode = std::move(SkUnicode::Make());
162     if (nullptr == fUnicode) {
163         return false;
164     }
165 
166     // Get white spaces
167     fUnicode->forEachCodepoint(fText.c_str(), fText.size(),
168        [this](SkUnichar unichar, int32_t start, int32_t end) {
169             if (fUnicode->isWhitespace(unichar)) {
170                 for (auto i = start; i < end; ++i) {
171                     fCodeUnitProperties[i] |=  CodeUnitFlags::kPartOfWhiteSpace;
172                 }
173             }
174        });
175 
176     // Get line breaks
177     std::vector<SkUnicode::LineBreakBefore> lineBreaks;
178     if (!fUnicode->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) {
179         return false;
180     }
181     for (auto& lineBreak : lineBreaks) {
182         fCodeUnitProperties[lineBreak.pos] |= lineBreak.breakType == SkUnicode::LineBreakType::kHardLineBreak
183                                            ? CodeUnitFlags::kHardLineBreakBefore
184                                            : CodeUnitFlags::kSoftLineBreakBefore;
185     }
186 
187     // Get graphemes
188     std::vector<SkUnicode::Position> graphemes;
189     if (!fUnicode->getGraphemes(fText.c_str(), fText.size(), &graphemes)) {
190         return false;
191     }
192     for (auto pos : graphemes) {
193         fCodeUnitProperties[pos] |= CodeUnitFlags::kGraphemeStart;
194     }
195 
196     return true;
197 }
198 
markGlyphs()199 void Processor::markGlyphs() {
200     for (auto& run : fRuns) {
201         for (auto index : run.fClusters) {
202             fCodeUnitProperties[index] |= CodeUnitFlags::kGlyphStart;
203         }
204     }
205 }
206 
207 template<typename Visitor>
iterateByVisualOrder(CodeUnitFlags units,Visitor visitor)208 void Processor::iterateByVisualOrder(CodeUnitFlags units, Visitor visitor) {
209     SkSize offset = SkSize::MakeEmpty();
210     for (auto& line : fLines) {
211         offset.fWidth = 0;
212         for (auto& runIndex : line.fRunsInVisualOrder) {
213             auto& run = fRuns[runIndex];
214 
215             auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
216             auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
217 
218             Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end());
219             Range glyphRange(startGlyph, endGlyph);
220             for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) {
221                 auto textIndex = run.fClusters[glyphIndex];
222                 if (glyphIndex < endGlyph && !this->hasProperty(textIndex, units)) {
223                     continue;
224                 }
225                 textRange.fEnd = textIndex;
226                 glyphRange.fEnd = glyphIndex;
227                 visitor(offset, line.fTextMetrics.baseline(), &run, textRange, glyphRange, this->fCodeUnitProperties[textIndex]);
228                 textRange.fStart = textIndex;
229                 glyphRange.fStart = glyphIndex;
230                 offset.fWidth += run.calculateWidth(glyphRange);
231             }
232         }
233         offset.fHeight += line.fTextMetrics.height();
234     }
235 }
236 
237 template<typename Visitor>
iterateByVisualOrder(SkTArray<DecorBlock,true> & decorBlocks,Visitor visitor)238 void Processor::iterateByVisualOrder(SkTArray<DecorBlock, true>& decorBlocks, Visitor visitor) {
239 
240     this->sortDecorBlocks(decorBlocks);
241     // Decor blocks have to be sorted by text cannot intersect but can skip some parts of the text
242     // (in which case we use default text style from paragraph style)
243     // The edges of the decor blocks don't have to match glyph, grapheme or even unicode code point edges
244     // It's out responsibility to adjust them to some reasonable values
245     // [a:b) -> [c:d) where
246     // c is closest GG cluster edge to a from the left and d is closest GG cluster edge to b from the left
247 
248     DecorBlock* currentBlock = &decorBlocks[0];
249     SkSize offset = SkSize::MakeEmpty();
250     for (auto& line : fLines) {
251         offset.fWidth = 0;
252         for (auto& runIndex : line.fRunsInVisualOrder) {
253             auto& run = fRuns[runIndex];
254             // The run edges are good (aligned to GGC)
255             // "ABCdef" -> "defCBA"
256             // "AB": red
257             // "Cd": green
258             // "ef": blue
259             // green[d] blue[ef] green [C] red [BA]
260             auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0;
261             auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size();
262 
263             TextRange textRange(run.fClusters[startGlyph], run.fClusters[endGlyph]);
264             GlyphRange glyphRange(startGlyph, endGlyph);
265 
266             SkASSERT(currentBlock->fRange.fStart <= textRange.fStart);
267             for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) {
268                 auto textIndex = run.fClusters[glyphIndex];
269                 if (run.leftToRight() && textIndex < currentBlock->fRange.fEnd) {
270                     continue;
271                 } else if (!run.leftToRight() && textIndex > currentBlock->fRange.fStart) {
272                     continue;
273                 }
274 
275                 if (run.leftToRight()) {
276                     textRange.fEnd = textIndex;
277                 } else {
278                     textRange.fStart = textIndex;
279                 }
280                 glyphRange.fEnd = glyphIndex;
281                 SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
282                 visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
283                 if (run.leftToRight()) {
284                     textRange.fStart = textIndex;
285                 } else {
286                     textRange.fEnd = textIndex;
287                 }
288                 glyphRange.fStart = glyphIndex;
289                 offset.fWidth += run.calculateWidth(glyphRange);
290             }
291 
292             // The last line
293             if (run.leftToRight()) {
294                 textRange.fEnd = run.fClusters[endGlyph];
295             } else {
296                 textRange.fStart = run.fClusters[endGlyph];
297             }
298             glyphRange.fEnd = endGlyph;
299             SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight);
300             visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock);
301         }
302         offset.fHeight += line.fTextMetrics.height();
303     }
304 }
305 
306 } // namespace text
307 } // namespace skia
308