// Copyright 2021 Google LLC. #include #include "experimental/sktext/include/Processor.h" #include "experimental/sktext/src/Formatter.h" #include "experimental/sktext/src/Shaper.h" #include "experimental/sktext/src/Wrapper.h" namespace skia { namespace text { // The result of shaping is a set of Runs placed on one endless line // It has all the glyph information bool Processor::shape(TextFontStyle fontStyle, SkTArray fontBlocks) { if (fUnicode == nullptr) { return false; } fFontBlocks = std::move(fontBlocks); Shaper shaper(this, fontStyle); if (!shaper.process()) { return false; } this->markGlyphs(); return true; } // TODO: we can wrap to any shape, not just a rectangle // The result of wrapping is a set of Lines that fit the required sizes and // contain all the glyph information bool Processor::wrap(SkScalar width, SkScalar height) { Wrapper wrapper(this, width, height); if (!wrapper.process()) { return false; } return true; } // The result of formatting is a possible shift of glyphs as the format type requires bool Processor::format(TextFormatStyle formatStyle) { Formatter formatter(this, formatStyle); if (!formatter.process()) { return false; } return true; } // Once the text is decorated you can iterate it by segments (intersect of run, decor block and line) bool Processor::decorate(SkTArray decorBlocks) { this->iterateByVisualOrder(decorBlocks, [&](SkSize offset, SkScalar baseline, const TextRun* run, TextRange textRange, GlyphRange glyphRange, const DecorBlock& block) { SkTextBlobBuilder builder; const auto& blobBuffer = builder.allocRunPos(run->fFont, SkToInt(glyphRange.width())); sk_careful_memcpy(blobBuffer.glyphs, run->fGlyphs.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkGlyphID)); sk_careful_memcpy(blobBuffer.points(), run->fPositions.data() + glyphRange.fStart, glyphRange.width() * sizeof(SkPoint)); offset.fHeight += baseline; fTextOutputs.emplace_back(builder.make(), *block.fForegroundColor, offset); }); return true; } // All at once bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar x, SkScalar y) { return drawText(text, canvas, TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), x, y); } bool Processor::drawText(const char* text, SkCanvas* canvas, SkScalar width) { return drawText(text, canvas, TextFormatStyle(TextAlign::kLeft, TextDirection::kLtr), SK_ColorBLACK, SK_ColorWHITE, SkString("Roboto"), 14, SkFontStyle::Normal(), SkSize::Make(width, SK_ScalarInfinity), 0, 0); } 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) { return drawText(text, canvas, textFormat, foreground, background, fontFamily, fontSize, fontStyle, SkSize::Make(SK_ScalarInfinity, SK_ScalarInfinity), x, y); } bool Processor::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) { SkString str(text); TextRange textRange(0, str.size()); Processor processor(str); if (!processor.computeCodeUnitProperties()) { return false; } if (!processor.shape({ textFormat.fDefaultTextDirection, SkFontMgr::RefDefault()}, {{{ fontFamily, fontSize, fontStyle, textRange }}})) { return false; } if (!processor.wrap(reqSize.fWidth, reqSize.fHeight)) { return false; } if (!processor.format(textFormat)) { return false; } SkTArray decor; SkPaint backgroundPaint; backgroundPaint.setColor(background); SkPaint foregroundPaint; foregroundPaint.setColor(foreground); if (!processor.decorate({{{&foregroundPaint, &backgroundPaint, textRange}}})) { return false; } for (auto& output : processor.fTextOutputs) { canvas->drawTextBlob(output.fTextBlob, x + output.fOffset.fWidth, y + output.fOffset.fHeight, output.fPaint); } return true; } // Also adjust the decoration block edges to cluster edges while we at it // to avoid an enormous amount of complications void Processor::sortDecorBlocks(SkTArray& decorBlocks) { // Soft the blocks std::sort(decorBlocks.begin(), decorBlocks.end(), [](const DecorBlock& a, const DecorBlock& b) { return a.fRange.fStart < b.fRange.fStart; }); // Walk through the blocks using the default when missing SkPaint* foreground = new SkPaint(); foreground->setColor(SK_ColorBLACK); std::stack defaultBlocks; defaultBlocks.emplace(foreground, nullptr, TextRange(0, fText.size())); size_t start = 0; for (auto& block : decorBlocks) { this->adjustLeft(&block.fRange.fStart); this->adjustLeft(&block.fRange.fEnd); SkASSERT(start <= block.fRange.fStart); if (start < block.fRange.fStart) { auto defaultBlock = defaultBlocks.top(); decorBlocks.emplace_back(defaultBlock.fForegroundColor, defaultBlock.fBackgroundColor, Range(start, block.fRange.fStart)); } start = block.fRange.fEnd; while (!defaultBlocks.empty()) { auto defaultBlock = defaultBlocks.top(); if (defaultBlock.fRange.fEnd <= block.fRange.fEnd) { defaultBlocks.pop(); } } defaultBlocks.push(block); } if (start < fText.size()) { auto defaultBlock = defaultBlocks.top(); decorBlocks.emplace_back(defaultBlock.fForegroundColor, defaultBlock.fBackgroundColor, Range(start, fText.size())); } } bool Processor::computeCodeUnitProperties() { fCodeUnitProperties.push_back_n(fText.size() + 1, CodeUnitFlags::kNoCodeUnitFlag); fUnicode = std::move(SkUnicode::Make()); if (nullptr == fUnicode) { return false; } // Get white spaces fUnicode->forEachCodepoint(fText.c_str(), fText.size(), [this](SkUnichar unichar, int32_t start, int32_t end) { if (fUnicode->isWhitespace(unichar)) { for (auto i = start; i < end; ++i) { fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfWhiteSpace; } } }); // Get line breaks std::vector lineBreaks; if (!fUnicode->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) { return false; } for (auto& lineBreak : lineBreaks) { fCodeUnitProperties[lineBreak.pos] |= lineBreak.breakType == SkUnicode::LineBreakType::kHardLineBreak ? CodeUnitFlags::kHardLineBreakBefore : CodeUnitFlags::kSoftLineBreakBefore; } // Get graphemes std::vector graphemes; if (!fUnicode->getGraphemes(fText.c_str(), fText.size(), &graphemes)) { return false; } for (auto pos : graphemes) { fCodeUnitProperties[pos] |= CodeUnitFlags::kGraphemeStart; } return true; } void Processor::markGlyphs() { for (auto& run : fRuns) { for (auto index : run.fClusters) { fCodeUnitProperties[index] |= CodeUnitFlags::kGlyphStart; } } } template void Processor::iterateByVisualOrder(CodeUnitFlags units, Visitor visitor) { SkSize offset = SkSize::MakeEmpty(); for (auto& line : fLines) { offset.fWidth = 0; for (auto& runIndex : line.fRunsInVisualOrder) { auto& run = fRuns[runIndex]; auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0; auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size(); Range textRange(run.fUtf8Range.begin(), run.fUtf8Range.end()); Range glyphRange(startGlyph, endGlyph); for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) { auto textIndex = run.fClusters[glyphIndex]; if (glyphIndex < endGlyph && !this->hasProperty(textIndex, units)) { continue; } textRange.fEnd = textIndex; glyphRange.fEnd = glyphIndex; visitor(offset, line.fTextMetrics.baseline(), &run, textRange, glyphRange, this->fCodeUnitProperties[textIndex]); textRange.fStart = textIndex; glyphRange.fStart = glyphIndex; offset.fWidth += run.calculateWidth(glyphRange); } } offset.fHeight += line.fTextMetrics.height(); } } template void Processor::iterateByVisualOrder(SkTArray& decorBlocks, Visitor visitor) { this->sortDecorBlocks(decorBlocks); // Decor blocks have to be sorted by text cannot intersect but can skip some parts of the text // (in which case we use default text style from paragraph style) // The edges of the decor blocks don't have to match glyph, grapheme or even unicode code point edges // It's out responsibility to adjust them to some reasonable values // [a:b) -> [c:d) where // c is closest GG cluster edge to a from the left and d is closest GG cluster edge to b from the left DecorBlock* currentBlock = &decorBlocks[0]; SkSize offset = SkSize::MakeEmpty(); for (auto& line : fLines) { offset.fWidth = 0; for (auto& runIndex : line.fRunsInVisualOrder) { auto& run = fRuns[runIndex]; // The run edges are good (aligned to GGC) // "ABCdef" -> "defCBA" // "AB": red // "Cd": green // "ef": blue // green[d] blue[ef] green [C] red [BA] auto startGlyph = runIndex == line.fTextStart.runIndex() ? line.fTextStart.glyphIndex() : 0; auto endGlyph = runIndex == line.fTextEnd.runIndex() ? line.fTextEnd.glyphIndex() : run.fGlyphs.size(); TextRange textRange(run.fClusters[startGlyph], run.fClusters[endGlyph]); GlyphRange glyphRange(startGlyph, endGlyph); SkASSERT(currentBlock->fRange.fStart <= textRange.fStart); for (auto glyphIndex = startGlyph; glyphIndex <= endGlyph; ++glyphIndex) { auto textIndex = run.fClusters[glyphIndex]; if (run.leftToRight() && textIndex < currentBlock->fRange.fEnd) { continue; } else if (!run.leftToRight() && textIndex > currentBlock->fRange.fStart) { continue; } if (run.leftToRight()) { textRange.fEnd = textIndex; } else { textRange.fStart = textIndex; } glyphRange.fEnd = glyphIndex; SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight); visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock); if (run.leftToRight()) { textRange.fStart = textIndex; } else { textRange.fEnd = textIndex; } glyphRange.fStart = glyphIndex; offset.fWidth += run.calculateWidth(glyphRange); } // The last line if (run.leftToRight()) { textRange.fEnd = run.fClusters[endGlyph]; } else { textRange.fStart = run.fClusters[endGlyph]; } glyphRange.fEnd = endGlyph; SkSize shift = SkSize::Make(offset.fWidth - run.fPositions[startGlyph].fX, offset.fHeight); visitor(shift, line.fTextMetrics.baseline(), &run, textRange, glyphRange, *currentBlock); } offset.fHeight += line.fTextMetrics.height(); } } } // namespace text } // namespace skia