| /* |
| * Copyright 2016 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkShaper.h" |
| #include "SkFontMetrics.h" |
| #include "SkStream.h" |
| #include "SkTo.h" |
| #include "SkTypeface.h" |
| #include "SkUTF.h" |
| |
| struct SkShaper::Impl {}; |
| SkShaper::SkShaper() : fImpl(nullptr) {} |
| |
| SkShaper::~SkShaper() {} |
| |
| bool SkShaper::good() const { return true; } |
| |
| static inline bool is_breaking_whitespace(SkUnichar c) { |
| switch (c) { |
| case 0x0020: // SPACE |
| //case 0x00A0: // NO-BREAK SPACE |
| case 0x1680: // OGHAM SPACE MARK |
| case 0x180E: // MONGOLIAN VOWEL SEPARATOR |
| case 0x2000: // EN QUAD |
| case 0x2001: // EM QUAD |
| case 0x2002: // EN SPACE (nut) |
| case 0x2003: // EM SPACE (mutton) |
| case 0x2004: // THREE-PER-EM SPACE (thick space) |
| case 0x2005: // FOUR-PER-EM SPACE (mid space) |
| case 0x2006: // SIX-PER-EM SPACE |
| case 0x2007: // FIGURE SPACE |
| case 0x2008: // PUNCTUATION SPACE |
| case 0x2009: // THIN SPACE |
| case 0x200A: // HAIR SPACE |
| case 0x200B: // ZERO WIDTH SPACE |
| case 0x202F: // NARROW NO-BREAK SPACE |
| case 0x205F: // MEDIUM MATHEMATICAL SPACE |
| case 0x3000: // IDEOGRAPHIC SPACE |
| //case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static size_t linebreak(const char text[], const char stop[], |
| const SkFont& font, SkScalar width, |
| SkScalar* advance, |
| size_t* trailing) |
| { |
| SkScalar accumulatedWidth = 0; |
| int glyphIndex = 0; |
| const char* start = text; |
| const char* word_start = text; |
| bool prevWS = true; |
| *trailing = 0; |
| |
| while (text < stop) { |
| const char* prevText = text; |
| SkUnichar uni = SkUTF::NextUTF8(&text, stop); |
| accumulatedWidth += advance[glyphIndex++]; |
| bool currWS = is_breaking_whitespace(uni); |
| |
| if (!currWS && prevWS) { |
| word_start = prevText; |
| } |
| prevWS = currWS; |
| |
| if (width < accumulatedWidth) { |
| if (currWS) { |
| // eat the rest of the whitespace |
| const char* next = text; |
| while (next < stop && is_breaking_whitespace(SkUTF::NextUTF8(&next, stop))) { |
| text = next; |
| } |
| if (trailing) { |
| *trailing = text - prevText; |
| } |
| } else { |
| // backup until a whitespace (or 1 char) |
| if (word_start == start) { |
| if (prevText > start) { |
| text = prevText; |
| } |
| } else { |
| text = word_start; |
| } |
| } |
| break; |
| } |
| |
| if ('\n' == uni) { |
| size_t ret = text - start; |
| size_t lineBreakSize = 1; |
| if (text < stop) { |
| uni = SkUTF::NextUTF8(&text, stop); |
| if ('\r' == uni) { |
| ret = text - start; |
| ++lineBreakSize; |
| } |
| } |
| if (trailing) { |
| *trailing = lineBreakSize; |
| } |
| return ret; |
| } |
| |
| if ('\r' == uni) { |
| size_t ret = text - start; |
| size_t lineBreakSize = 1; |
| if (text < stop) { |
| uni = SkUTF::NextUTF8(&text, stop); |
| if ('\n' == uni) { |
| ret = text - start; |
| ++lineBreakSize; |
| } |
| } |
| if (trailing) { |
| *trailing = lineBreakSize; |
| } |
| return ret; |
| } |
| } |
| |
| return text - start; |
| } |
| |
| SkPoint SkShaper::shape(RunHandler* handler, |
| const SkFont& font, |
| const char* utf8text, |
| size_t textBytes, |
| bool leftToRight, |
| SkPoint point, |
| SkScalar width) const { |
| sk_ignore_unused_variable(leftToRight); |
| |
| int glyphCount = font.countText(utf8text, textBytes, SkTextEncoding::kUTF8); |
| if (glyphCount <= 0) { |
| return point; |
| } |
| |
| std::unique_ptr<SkGlyphID[]> glyphs(new SkGlyphID[glyphCount]); |
| font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8, glyphs.get(), glyphCount); |
| |
| std::unique_ptr<SkScalar[]> advances(new SkScalar[glyphCount]); |
| font.getWidthsBounds(glyphs.get(), glyphCount, advances.get(), nullptr, nullptr); |
| |
| SkFontMetrics metrics; |
| font.getMetrics(&metrics); |
| |
| size_t glyphOffset = 0; |
| size_t utf8Offset = 0; |
| while (0 < textBytes) { |
| point.fY -= metrics.fAscent; |
| |
| size_t bytesCollapsed; |
| size_t bytesConsumed = linebreak(utf8text, utf8text + textBytes, font, width, |
| advances.get() + glyphOffset, &bytesCollapsed); |
| size_t bytesVisible = bytesConsumed - bytesCollapsed; |
| |
| int numGlyphs = SkUTF::CountUTF8(utf8text, bytesVisible); |
| const RunHandler::RunInfo info = { |
| { font.measureText(utf8text, bytesVisible, SkTextEncoding::kUTF8), 0 }, |
| metrics.fAscent, |
| metrics.fDescent, |
| metrics.fLeading, |
| }; |
| const auto buffer = handler->newRunBuffer(info, font, numGlyphs, |
| SkSpan<const char>(utf8text, bytesVisible)); |
| |
| memcpy(buffer.glyphs, glyphs.get() + glyphOffset, numGlyphs * sizeof(SkGlyphID)); |
| SkScalar position = point.fX; |
| for (int i = 0; i < numGlyphs; ++i) { |
| buffer.positions[i] = { position, point.fY }; |
| position += advances[i + glyphOffset]; |
| } |
| if (buffer.clusters) { |
| const char* txtPtr = utf8text; |
| for (int i = 0; i < numGlyphs; ++i) { |
| // Each character maps to exactly one glyph. |
| buffer.clusters[i] = SkToU32(txtPtr - utf8text + utf8Offset); |
| SkUTF::NextUTF8(&txtPtr, utf8text + textBytes); |
| } |
| } |
| handler->commitRun(); |
| handler->commitLine(); |
| |
| glyphOffset += SkUTF::CountUTF8(utf8text, bytesConsumed); |
| utf8Offset += bytesConsumed; |
| utf8text += bytesConsumed; |
| textBytes -= bytesConsumed; |
| point.fY += metrics.fDescent + metrics.fLeading; |
| } |
| |
| return point; |
| } |