/*
* 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 "SkFontMetrics.h"
#include "SkMakeUnique.h"
#include "SkShaper.h"
#include "SkStream.h"
#include "SkTo.h"
#include "SkTypeface.h"
#include "SkUTF.h"
class SkShaperPrimitive : public SkShaper {
public:
SkShaperPrimitive() {}
private:
SkPoint shape(RunHandler* handler,
const SkFont& srcFont,
const char* utf8text,
size_t textBytes,
bool leftToRight,
SkPoint point,
SkScalar width) const override;
};
std::unique_ptr<SkShaper> SkShaper::MakePrimitive() {
return skstd::make_unique<SkShaperPrimitive>();
}
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 SkShaperPrimitive::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;
}