From 264182c3f7d282e57a4b1d46fde3ef702b81c5c3 Mon Sep 17 00:00:00 2001 From: Herb Derby Date: Tue, 29 May 2018 15:53:40 -0400 Subject: Make SkShaper a module Change-Id: I3709e49ba865f14260660cc07a762b9ac837cb3c Reviewed-on: https://skia-review.googlesource.com/130602 Reviewed-by: Herb Derby Commit-Queue: Herb Derby --- modules/skshaper/BUILD.gn | 40 ++ modules/skshaper/include/SkShaper.h | 47 ++ modules/skshaper/src/SkShaper_harfbuzz.cpp | 728 ++++++++++++++++++++++++++++ modules/skshaper/src/SkShaper_primitive.cpp | 76 +++ 4 files changed, 891 insertions(+) create mode 100644 modules/skshaper/BUILD.gn create mode 100644 modules/skshaper/include/SkShaper.h create mode 100644 modules/skshaper/src/SkShaper_harfbuzz.cpp create mode 100644 modules/skshaper/src/SkShaper_primitive.cpp (limited to 'modules') diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn new file mode 100644 index 0000000000..eb67f90790 --- /dev/null +++ b/modules/skshaper/BUILD.gn @@ -0,0 +1,40 @@ +# Copyright 2018 Google Inc. +# +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +declare_args() { + skia_enable_skshaper = true +} + +config("public_config") { + if (skia_enable_skshaper) { + include_dirs = [ "include" ] + } +} + +source_set("skshaper") { + if (skia_enable_skshaper) { + public_configs = [ ":public_config" ] + public = [ "include/SkShaper.h" ] + deps = [ + "../..:skia", + ] + if (target_cpu == "wasm") { + sources = [ + "src/SkShaper_primitive.cpp", + ] + } else { + sources = [ + "src/SkShaper_harfbuzz.cpp", + ] + deps += [ + "//third_party/harfbuzz", + "//third_party/icu", + ] + } + configs += [ "../../:skia_private" ] + + } +} + diff --git a/modules/skshaper/include/SkShaper.h b/modules/skshaper/include/SkShaper.h new file mode 100644 index 0000000000..190a4d834e --- /dev/null +++ b/modules/skshaper/include/SkShaper.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkShaper_DEFINED +#define SkShaper_DEFINED + +#include + +#include "SkPoint.h" +#include "SkTypeface.h" + +class SkPaint; +class SkTextBlobBuilder; + +/** + Shapes text using HarfBuzz and places the shaped text into a + TextBlob. + + If compiled without HarfBuzz, fall back on SkPaint::textToGlyphs. + */ +class SkShaper { +public: + SkShaper(sk_sp face); + ~SkShaper(); + + bool good() const; + SkPoint shape(SkTextBlobBuilder* dest, + const SkPaint& srcPaint, + const char* utf8text, + size_t textBytes, + bool leftToRight, + SkPoint point, + SkScalar width) const; + +private: + SkShaper(const SkShaper&) = delete; + SkShaper& operator=(const SkShaper&) = delete; + + struct Impl; + std::unique_ptr fImpl; +}; + +#endif // SkShaper_DEFINED diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp new file mode 100644 index 0000000000..40414d9a79 --- /dev/null +++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp @@ -0,0 +1,728 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "SkFontMgr.h" +#include "SkLoadICU.h" +#include "SkOnce.h" +#include "SkShaper.h" +#include "SkStream.h" +#include "SkTDPQueue.h" +#include "SkTLazy.h" +#include "SkTemplates.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" +#include "SkUtils.h" + +namespace { +template using resource = std::unique_ptr>; +using HBBlob = resource; +using HBFace = resource; +using HBFont = resource; +using HBBuffer = resource; +using ICUBiDi = resource; + +HBBlob stream_to_blob(std::unique_ptr asset) { + size_t size = asset->getLength(); + HBBlob blob; + if (const void* base = asset->getMemoryBase()) { + blob.reset(hb_blob_create((char*)base, SkToUInt(size), + HB_MEMORY_MODE_READONLY, asset.release(), + [](void* p) { delete (SkStreamAsset*)p; })); + } else { + // SkDebugf("Extra SkStreamAsset copy\n"); + void* ptr = size ? sk_malloc_throw(size) : nullptr; + asset->read(ptr, size); + blob.reset(hb_blob_create((char*)ptr, SkToUInt(size), + HB_MEMORY_MODE_READONLY, ptr, sk_free)); + } + SkASSERT(blob); + hb_blob_make_immutable(blob.get()); + return blob; +} + +HBFont create_hb_font(SkTypeface* tf) { + int index; + HBBlob blob(stream_to_blob(std::unique_ptr(tf->openStream(&index)))); + HBFace face(hb_face_create(blob.get(), (unsigned)index)); + SkASSERT(face); + if (!face) { + return nullptr; + } + hb_face_set_index(face.get(), (unsigned)index); + hb_face_set_upem(face.get(), tf->getUnitsPerEm()); + + HBFont font(hb_font_create(face.get())); + SkASSERT(font); + if (!font) { + return nullptr; + } + hb_ot_font_set_funcs(font.get()); + int axis_count = tf->getVariationDesignPosition(nullptr, 0); + if (axis_count > 0) { + SkAutoSTMalloc<4, SkFontArguments::VariationPosition::Coordinate> axis_values(axis_count); + if (tf->getVariationDesignPosition(axis_values, axis_count) == axis_count) { + hb_font_set_variations(font.get(), + reinterpret_cast(axis_values.get()), + axis_count); + } + } + return font; +} + +class RunIterator { +public: + virtual ~RunIterator() {} + virtual void consume() = 0; + // Pointer one past the last (utf8) element in the current run. + virtual const char* endOfCurrentRun() const = 0; + virtual bool atEnd() const = 0; + bool operator<(const RunIterator& that) const { + return this->endOfCurrentRun() < that.endOfCurrentRun(); + } +}; + +class BiDiRunIterator : public RunIterator { +public: + static SkTLazy Make(const char* utf8, size_t utf8Bytes, UBiDiLevel level) { + SkTLazy ret; + + // ubidi only accepts utf16 (though internally it basically works on utf32 chars). + // We want an ubidi_setPara(UBiDi*, UText*, UBiDiLevel, UBiDiLevel*, UErrorCode*); + if (!SkTFitsIn(utf8Bytes)) { + SkDebugf("Bidi error: text too long"); + return ret; + } + icu::UnicodeString utf16 = icu::UnicodeString::fromUTF8(icu::StringPiece(utf8, utf8Bytes)); + + UErrorCode status = U_ZERO_ERROR; + ICUBiDi bidi(ubidi_openSized(utf16.length(), 0, &status)); + if (U_FAILURE(status)) { + SkDebugf("Bidi error: %s", u_errorName(status)); + return ret; + } + SkASSERT(bidi); + + // The required lifetime of utf16 isn't well documented. + // It appears it isn't used after ubidi_setPara except through ubidi_getText. + ubidi_setPara(bidi.get(), utf16.getBuffer(), utf16.length(), level, nullptr, &status); + if (U_FAILURE(status)) { + SkDebugf("Bidi error: %s", u_errorName(status)); + return ret; + } + + ret.init(utf8, std::move(bidi)); + return ret; + } + BiDiRunIterator(const char* utf8, ICUBiDi bidi) + : fBidi(std::move(bidi)) + , fEndOfCurrentRun(utf8) + , fUTF16LogicalPosition(0) + , fLevel(UBIDI_DEFAULT_LTR) + {} + void consume() override { + SkASSERT(fUTF16LogicalPosition < ubidi_getLength(fBidi.get())); + int32_t endPosition = ubidi_getLength(fBidi.get()); + fLevel = ubidi_getLevelAt(fBidi.get(), fUTF16LogicalPosition); + SkUnichar u = SkUTF8_NextUnichar(&fEndOfCurrentRun); + fUTF16LogicalPosition += SkUTF16_FromUnichar(u); + UBiDiLevel level; + while (fUTF16LogicalPosition < endPosition) { + level = ubidi_getLevelAt(fBidi.get(), fUTF16LogicalPosition); + if (level != fLevel) { + break; + } + u = SkUTF8_NextUnichar(&fEndOfCurrentRun); + fUTF16LogicalPosition += SkUTF16_FromUnichar(u); + } + } + const char* endOfCurrentRun() const override { + return fEndOfCurrentRun; + } + bool atEnd() const override { + return fUTF16LogicalPosition == ubidi_getLength(fBidi.get()); + } + + UBiDiLevel currentLevel() const { + return fLevel; + } +private: + ICUBiDi fBidi; + const char* fEndOfCurrentRun; + int32_t fUTF16LogicalPosition; + UBiDiLevel fLevel; +}; + +class ScriptRunIterator : public RunIterator { +public: + static SkTLazy Make(const char* utf8, size_t utf8Bytes, + hb_unicode_funcs_t* hbUnicode) + { + SkTLazy ret; + ret.init(utf8, utf8Bytes, hbUnicode); + return ret; + } + ScriptRunIterator(const char* utf8, size_t utf8Bytes, hb_unicode_funcs_t* hbUnicode) + : fCurrent(utf8), fEnd(fCurrent + utf8Bytes) + , fHBUnicode(hbUnicode) + , fCurrentScript(HB_SCRIPT_UNKNOWN) + {} + void consume() override { + SkASSERT(fCurrent < fEnd); + SkUnichar u = SkUTF8_NextUnichar(&fCurrent); + fCurrentScript = hb_unicode_script(fHBUnicode, u); + while (fCurrent < fEnd) { + const char* prev = fCurrent; + u = SkUTF8_NextUnichar(&fCurrent); + const hb_script_t script = hb_unicode_script(fHBUnicode, u); + if (script != fCurrentScript) { + if (fCurrentScript == HB_SCRIPT_INHERITED || fCurrentScript == HB_SCRIPT_COMMON) { + fCurrentScript = script; + } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) { + continue; + } else { + fCurrent = prev; + break; + } + } + } + if (fCurrentScript == HB_SCRIPT_INHERITED) { + fCurrentScript = HB_SCRIPT_COMMON; + } + } + const char* endOfCurrentRun() const override { + return fCurrent; + } + bool atEnd() const override { + return fCurrent == fEnd; + } + + hb_script_t currentScript() const { + return fCurrentScript; + } +private: + const char* fCurrent; + const char* fEnd; + hb_unicode_funcs_t* fHBUnicode; + hb_script_t fCurrentScript; +}; + +class FontRunIterator : public RunIterator { +public: + static SkTLazy Make(const char* utf8, size_t utf8Bytes, + sk_sp typeface, + hb_font_t* hbFace, + sk_sp fallbackMgr) + { + SkTLazy ret; + ret.init(utf8, utf8Bytes, std::move(typeface), hbFace, std::move(fallbackMgr)); + return ret; + } + FontRunIterator(const char* utf8, size_t utf8Bytes, sk_sp typeface, + hb_font_t* hbFace, sk_sp fallbackMgr) + : fCurrent(utf8), fEnd(fCurrent + utf8Bytes) + , fFallbackMgr(std::move(fallbackMgr)) + , fHBFont(hbFace), fTypeface(std::move(typeface)) + , fFallbackHBFont(nullptr), fFallbackTypeface(nullptr) + , fCurrentHBFont(fHBFont), fCurrentTypeface(fTypeface.get()) + {} + void consume() override { + SkASSERT(fCurrent < fEnd); + SkUnichar u = SkUTF8_NextUnichar(&fCurrent); + // If the starting typeface can handle this character, use it. + if (fTypeface->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1)) { + fFallbackTypeface.reset(); + // If not, try to find a fallback typeface + } else { + fFallbackTypeface.reset(fFallbackMgr->matchFamilyStyleCharacter( + nullptr, fTypeface->fontStyle(), nullptr, 0, u)); + } + + if (fFallbackTypeface) { + fFallbackHBFont = create_hb_font(fFallbackTypeface.get()); + fCurrentTypeface = fFallbackTypeface.get(); + fCurrentHBFont = fFallbackHBFont.get(); + } else { + fFallbackHBFont.reset(); + fCurrentTypeface = fTypeface.get(); + fCurrentHBFont = fHBFont; + } + + while (fCurrent < fEnd) { + const char* prev = fCurrent; + u = SkUTF8_NextUnichar(&fCurrent); + + // If using a fallback and the initial typeface has this character, stop fallback. + if (fFallbackTypeface && + fTypeface->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1)) + { + fCurrent = prev; + return; + } + // If the current typeface cannot handle this character, stop using it. + if (!fCurrentTypeface->charsToGlyphs(&u, SkTypeface::kUTF32_Encoding, nullptr, 1)) { + fCurrent = prev; + return; + } + } + } + const char* endOfCurrentRun() const override { + return fCurrent; + } + bool atEnd() const override { + return fCurrent == fEnd; + } + + SkTypeface* currentTypeface() const { + return fCurrentTypeface; + } + hb_font_t* currentHBFont() const { + return fCurrentHBFont; + } +private: + const char* fCurrent; + const char* fEnd; + sk_sp fFallbackMgr; + hb_font_t* fHBFont; + sk_sp fTypeface; + HBFont fFallbackHBFont; + sk_sp fFallbackTypeface; + hb_font_t* fCurrentHBFont; + SkTypeface* fCurrentTypeface; +}; + +class RunIteratorQueue { +public: + void insert(RunIterator* runIterator) { + fRunIterators.insert(runIterator); + } + + bool advanceRuns() { + const RunIterator* leastRun = fRunIterators.peek(); + if (leastRun->atEnd()) { + SkASSERT(this->allRunsAreAtEnd()); + return false; + } + const char* leastEnd = leastRun->endOfCurrentRun(); + RunIterator* currentRun = nullptr; + SkDEBUGCODE(const char* previousEndOfCurrentRun); + while ((currentRun = fRunIterators.peek())->endOfCurrentRun() <= leastEnd) { + fRunIterators.pop(); + SkDEBUGCODE(previousEndOfCurrentRun = currentRun->endOfCurrentRun()); + currentRun->consume(); + SkASSERT(previousEndOfCurrentRun < currentRun->endOfCurrentRun()); + fRunIterators.insert(currentRun); + } + return true; + } + + const char* endOfCurrentRun() const { + return fRunIterators.peek()->endOfCurrentRun(); + } + +private: + bool allRunsAreAtEnd() const { + for (int i = 0; i < fRunIterators.count(); ++i) { + if (!fRunIterators.at(i)->atEnd()) { + return false; + } + } + return true; + } + + static bool CompareRunIterator(RunIterator* const& a, RunIterator* const& b) { + return *a < *b; + } + SkTDPQueue fRunIterators; +}; + +struct ShapedGlyph { + SkGlyphID fID; + uint32_t fCluster; + SkPoint fOffset; + SkVector fAdvance; + bool fMayLineBreakBefore; + bool fMustLineBreakBefore; + bool fHasVisual; +}; +struct ShapedRun { + ShapedRun(const char* utf8Start, const char* utf8End, int numGlyphs, const SkPaint& paint, + UBiDiLevel level, std::unique_ptr glyphs) + : fUtf8Start(utf8Start), fUtf8End(utf8End), fNumGlyphs(numGlyphs), fPaint(paint) + , fLevel(level), fGlyphs(std::move(glyphs)) + {} + + const char* fUtf8Start; + const char* fUtf8End; + int fNumGlyphs; + SkPaint fPaint; + UBiDiLevel fLevel; + std::unique_ptr fGlyphs; +}; + +static constexpr bool is_LTR(UBiDiLevel level) { + return (level & 1) == 0; +} + +static void append(SkTextBlobBuilder* b, const ShapedRun& run, int start, int end, SkPoint* p) { + unsigned len = end - start; + auto runBuffer = b->allocRunTextPos(run.fPaint, len, run.fUtf8End - run.fUtf8Start, SkString()); + memcpy(runBuffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start); + + for (unsigned i = 0; i < len; i++) { + // Glyphs are in logical order, but output ltr since PDF readers seem to expect that. + const ShapedGlyph& glyph = run.fGlyphs[is_LTR(run.fLevel) ? start + i : end - 1 - i]; + runBuffer.glyphs[i] = glyph.fID; + runBuffer.clusters[i] = glyph.fCluster; + reinterpret_cast(runBuffer.pos)[i] = + SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY); + p->fX += glyph.fAdvance.fX; + p->fY += glyph.fAdvance.fY; + } +} + +struct ShapedRunGlyphIterator { + ShapedRunGlyphIterator(const SkTArray& origRuns) + : fRuns(&origRuns), fRunIndex(0), fGlyphIndex(0) + { } + + ShapedRunGlyphIterator(const ShapedRunGlyphIterator& that) = default; + ShapedRunGlyphIterator& operator=(const ShapedRunGlyphIterator& that) = default; + bool operator==(const ShapedRunGlyphIterator& that) const { + return fRuns == that.fRuns && + fRunIndex == that.fRunIndex && + fGlyphIndex == that.fGlyphIndex; + } + bool operator!=(const ShapedRunGlyphIterator& that) const { + return fRuns != that.fRuns || + fRunIndex != that.fRunIndex || + fGlyphIndex != that.fGlyphIndex; + } + + ShapedGlyph* next() { + const SkTArray& runs = *fRuns; + SkASSERT(fRunIndex < runs.count()); + SkASSERT(fGlyphIndex < runs[fRunIndex].fNumGlyphs); + + ++fGlyphIndex; + if (fGlyphIndex == runs[fRunIndex].fNumGlyphs) { + fGlyphIndex = 0; + ++fRunIndex; + if (fRunIndex >= runs.count()) { + return nullptr; + } + } + return &runs[fRunIndex].fGlyphs[fGlyphIndex]; + } + + ShapedGlyph* current() { + const SkTArray& runs = *fRuns; + if (fRunIndex >= runs.count()) { + return nullptr; + } + return &runs[fRunIndex].fGlyphs[fGlyphIndex]; + } + + const SkTArray* fRuns; + int fRunIndex; + int fGlyphIndex; +}; + +} // namespace + +struct SkShaper::Impl { + HBFont fHarfBuzzFont; + HBBuffer fBuffer; + sk_sp fTypeface; + std::unique_ptr fBreakIterator; +}; + +SkShaper::SkShaper(sk_sp tf) : fImpl(new Impl) { + SkOnce once; + once([] { SkLoadICU(); }); + + fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault(); + fImpl->fHarfBuzzFont = create_hb_font(fImpl->fTypeface.get()); + SkASSERT(fImpl->fHarfBuzzFont); + fImpl->fBuffer.reset(hb_buffer_create()); + SkASSERT(fImpl->fBuffer); + + icu::Locale thai("th"); + UErrorCode status = U_ZERO_ERROR; + fImpl->fBreakIterator.reset(icu::BreakIterator::createLineInstance(thai, status)); + if (U_FAILURE(status)) { + SkDebugf("Could not create break iterator: %s", u_errorName(status)); + SK_ABORT(""); + } +} + +SkShaper::~SkShaper() {} + +bool SkShaper::good() const { + return fImpl->fHarfBuzzFont && + fImpl->fBuffer && + fImpl->fTypeface && + fImpl->fBreakIterator; +} + +SkPoint SkShaper::shape(SkTextBlobBuilder* builder, + const SkPaint& srcPaint, + const char* utf8, + size_t utf8Bytes, + bool leftToRight, + SkPoint point, + SkScalar width) const { + sk_sp fontMgr = SkFontMgr::RefDefault(); + SkASSERT(builder); + UBiDiLevel defaultLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL; + //hb_script_t script = ... + + SkTArray runs; +{ + RunIteratorQueue runSegmenter; + + SkTLazy maybeBidi(BiDiRunIterator::Make(utf8, utf8Bytes, defaultLevel)); + BiDiRunIterator* bidi = maybeBidi.getMaybeNull(); + if (!bidi) { + return point; + } + runSegmenter.insert(bidi); + + hb_unicode_funcs_t* hbUnicode = hb_buffer_get_unicode_funcs(fImpl->fBuffer.get()); + SkTLazy maybeScript(ScriptRunIterator::Make(utf8, utf8Bytes, hbUnicode)); + ScriptRunIterator* script = maybeScript.getMaybeNull(); + if (!script) { + return point; + } + runSegmenter.insert(script); + + SkTLazy maybeFont(FontRunIterator::Make(utf8, utf8Bytes, + fImpl->fTypeface, + fImpl->fHarfBuzzFont.get(), + std::move(fontMgr))); + FontRunIterator* font = maybeFont.getMaybeNull(); + if (!font) { + return point; + } + runSegmenter.insert(font); + + icu::BreakIterator& breakIterator = *fImpl->fBreakIterator; + { + UErrorCode status = U_ZERO_ERROR; + UText utf8UText = UTEXT_INITIALIZER; + utext_openUTF8(&utf8UText, utf8, utf8Bytes, &status); + std::unique_ptr> autoClose(&utf8UText); + if (U_FAILURE(status)) { + SkDebugf("Could not create utf8UText: %s", u_errorName(status)); + return point; + } + breakIterator.setText(&utf8UText, status); + //utext_close(&utf8UText); + if (U_FAILURE(status)) { + SkDebugf("Could not setText on break iterator: %s", u_errorName(status)); + return point; + } + } + + const char* utf8Start = nullptr; + const char* utf8End = utf8; + while (runSegmenter.advanceRuns()) { + utf8Start = utf8End; + utf8End = runSegmenter.endOfCurrentRun(); + + hb_buffer_t* buffer = fImpl->fBuffer.get(); + SkAutoTCallVProc autoClearBuffer(buffer); + hb_buffer_set_content_type(buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); + hb_buffer_set_cluster_level(buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); + + // Populate the hb_buffer directly with utf8 cluster indexes. + const char* utf8Current = utf8Start; + while (utf8Current < utf8End) { + unsigned int cluster = utf8Current - utf8Start; + hb_codepoint_t u = SkUTF8_NextUnichar(&utf8Current); + hb_buffer_add(buffer, u, cluster); + } + + size_t utf8runLength = utf8End - utf8Start; + if (!SkTFitsIn(utf8runLength)) { + SkDebugf("Shaping error: utf8 too long"); + return point; + } + hb_buffer_set_script(buffer, script->currentScript()); + hb_direction_t direction = is_LTR(bidi->currentLevel()) ? HB_DIRECTION_LTR:HB_DIRECTION_RTL; + hb_buffer_set_direction(buffer, direction); + // TODO: language + hb_buffer_guess_segment_properties(buffer); + // TODO: features + hb_shape(font->currentHBFont(), buffer, nullptr, 0); + unsigned len = hb_buffer_get_length(buffer); + if (len == 0) { + continue; + } + + if (direction == HB_DIRECTION_RTL) { + // Put the clusters back in logical order. + // Note that the advances remain ltr. + hb_buffer_reverse(buffer); + } + hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, nullptr); + hb_glyph_position_t* pos = hb_buffer_get_glyph_positions(buffer, nullptr); + + if (!SkTFitsIn(len)) { + SkDebugf("Shaping error: too many glyphs"); + return point; + } + + SkPaint paint(srcPaint); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setTypeface(sk_ref_sp(font->currentTypeface())); + ShapedRun& run = runs.emplace_back(utf8Start, utf8End, len, paint, bidi->currentLevel(), + std::unique_ptr(new ShapedGlyph[len])); + int scaleX, scaleY; + hb_font_get_scale(font->currentHBFont(), &scaleX, &scaleY); + double textSizeY = run.fPaint.getTextSize() / scaleY; + double textSizeX = run.fPaint.getTextSize() / scaleX * run.fPaint.getTextScaleX(); + for (unsigned i = 0; i < len; i++) { + ShapedGlyph& glyph = run.fGlyphs[i]; + glyph.fID = info[i].codepoint; + glyph.fCluster = info[i].cluster; + glyph.fOffset.fX = pos[i].x_offset * textSizeX; + glyph.fOffset.fY = pos[i].y_offset * textSizeY; + glyph.fAdvance.fX = pos[i].x_advance * textSizeX; + glyph.fAdvance.fY = pos[i].y_advance * textSizeY; + glyph.fHasVisual = true; //!font->currentTypeface()->glyphBoundsAreZero(glyph.fID); + //info->mask safe_to_break; + glyph.fMustLineBreakBefore = false; + } + + int32_t clusterOffset = utf8Start - utf8; + uint32_t previousCluster = 0xFFFFFFFF; + for (unsigned i = 0; i < len; ++i) { + ShapedGlyph& glyph = run.fGlyphs[i]; + int32_t glyphCluster = glyph.fCluster + clusterOffset; + int32_t breakIteratorCurrent = breakIterator.current(); + while (breakIteratorCurrent != icu::BreakIterator::DONE && + breakIteratorCurrent < glyphCluster) + { + breakIteratorCurrent = breakIterator.next(); + } + glyph.fMayLineBreakBefore = glyph.fCluster != previousCluster && + breakIteratorCurrent == glyphCluster; + previousCluster = glyph.fCluster; + } + } +} + +// Iterate over the glyphs in logical order to mark line endings. +{ + SkScalar widthSoFar = 0; + bool previousBreakValid = false; // Set when previousBreak is set to a valid candidate break. + bool canAddBreakNow = false; // Disallow line breaks before the first glyph of a run. + ShapedRunGlyphIterator previousBreak(runs); + ShapedRunGlyphIterator glyphIterator(runs); + while (ShapedGlyph* glyph = glyphIterator.current()) { + if (canAddBreakNow && glyph->fMayLineBreakBefore) { + previousBreakValid = true; + previousBreak = glyphIterator; + } + SkScalar glyphWidth = glyph->fAdvance.fX; + if (widthSoFar + glyphWidth < width) { + widthSoFar += glyphWidth; + glyphIterator.next(); + canAddBreakNow = true; + continue; + } + + if (widthSoFar == 0) { + // Adding just this glyph is too much, just break with this glyph + glyphIterator.next(); + previousBreak = glyphIterator; + } else if (!previousBreakValid) { + // No break opprotunity found yet, just break without this glyph + previousBreak = glyphIterator; + } + glyphIterator = previousBreak; + glyph = glyphIterator.current(); + if (glyph) { + glyph->fMustLineBreakBefore = true; + } + widthSoFar = 0; + previousBreakValid = false; + canAddBreakNow = false; + } +} + +// Reorder the runs and glyphs per line and write them out. + SkPoint currentPoint = point; +{ + ShapedRunGlyphIterator previousBreak(runs); + ShapedRunGlyphIterator glyphIterator(runs); + SkScalar maxAscent = 0; + SkScalar maxDescent = 0; + SkScalar maxLeading = 0; + int previousRunIndex = -1; + while (glyphIterator.current()) { + int runIndex = glyphIterator.fRunIndex; + int glyphIndex = glyphIterator.fGlyphIndex; + ShapedGlyph* nextGlyph = glyphIterator.next(); + + if (previousRunIndex != runIndex) { + SkPaint::FontMetrics metrics; + runs[runIndex].fPaint.getFontMetrics(&metrics); + maxAscent = SkTMin(maxAscent, metrics.fAscent); + maxDescent = SkTMax(maxDescent, metrics.fDescent); + maxLeading = SkTMax(maxLeading, metrics.fLeading); + previousRunIndex = runIndex; + } + + // Nothing can be written until the baseline is known. + if (!(nextGlyph == nullptr || nextGlyph->fMustLineBreakBefore)) { + continue; + } + + currentPoint.fY -= maxAscent; + + int numRuns = runIndex - previousBreak.fRunIndex + 1; + SkAutoSTMalloc<4, UBiDiLevel> runLevels(numRuns); + for (int i = 0; i < numRuns; ++i) { + runLevels[i] = runs[previousBreak.fRunIndex + i].fLevel; + } + SkAutoSTMalloc<4, int32_t> logicalFromVisual(numRuns); + ubidi_reorderVisual(runLevels, numRuns, logicalFromVisual); + + for (int i = 0; i < numRuns; ++i) { + int logicalIndex = previousBreak.fRunIndex + logicalFromVisual[i]; + + int startGlyphIndex = (logicalIndex == previousBreak.fRunIndex) + ? previousBreak.fGlyphIndex + : 0; + int endGlyphIndex = (logicalIndex == runIndex) + ? glyphIndex + 1 + : runs[logicalIndex].fNumGlyphs; + append(builder, runs[logicalIndex], startGlyphIndex, endGlyphIndex, ¤tPoint); + } + + currentPoint.fY += maxDescent + maxLeading; + currentPoint.fX = point.fX; + maxAscent = 0; + maxDescent = 0; + maxLeading = 0; + previousRunIndex = -1; + previousBreak = glyphIterator; + } +} + + return currentPoint; +} diff --git a/modules/skshaper/src/SkShaper_primitive.cpp b/modules/skshaper/src/SkShaper_primitive.cpp new file mode 100644 index 0000000000..06a8bec41c --- /dev/null +++ b/modules/skshaper/src/SkShaper_primitive.cpp @@ -0,0 +1,76 @@ +/* + * 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 "SkStream.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" + +struct SkShaper::Impl { + sk_sp fTypeface; +}; + +SkShaper::SkShaper(sk_sp tf) : fImpl(new Impl) { + fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault(); +} + +SkShaper::~SkShaper() {} + +bool SkShaper::good() const { return true; } + +// This example only uses public API, so we don't use SkUTF8_NextUnichar. +unsigned utf8_lead_byte_to_count(const char* ptr) { + uint8_t c = *(const uint8_t*)ptr; + SkASSERT(c <= 0xF7); + SkASSERT((c & 0xC0) != 0x80); + return (((0xE5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1; +} + +SkPoint SkShaper::shape(SkTextBlobBuilder* builder, + const SkPaint& srcPaint, + const char* utf8text, + size_t textBytes, + bool leftToRight, + SkPoint point, + SkScalar width) const { + sk_ignore_unused_variable(leftToRight); + + SkPaint paint(srcPaint); + paint.setTypeface(fImpl->fTypeface); + paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + int glyphCount = paint.countText(utf8text, textBytes); + if (glyphCount <= 0) { + return point; + } + SkRect bounds; + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + point.fY -= metrics.fAscent; + (void)paint.measureText(utf8text, textBytes, &bounds); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + const SkTextBlobBuilder::RunBuffer& runBuffer = + builder->allocRunTextPosH(paint, glyphCount, point.y(), textBytes, SkString(), &bounds); + memcpy(runBuffer.utf8text, utf8text, textBytes); + const char* txtPtr = utf8text; + for (int i = 0; i < glyphCount; ++i) { + // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph(). + runBuffer.clusters[i] = SkToU32(txtPtr - utf8text); + txtPtr += utf8_lead_byte_to_count(txtPtr); + SkASSERT(txtPtr <= utf8text + textBytes); + } + paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + (void)paint.textToGlyphs(utf8text, textBytes, runBuffer.glyphs); + (void)paint.getTextWidths(utf8text, textBytes, runBuffer.pos); + SkScalar x = point.x(); + for (int i = 0; i < glyphCount; ++i) { + SkScalar w = runBuffer.pos[i]; + runBuffer.pos[i] = x; + x += w; + } + point.fY += metrics.fDescent + metrics.fLeading; + + return point; +} -- cgit v1.2.3