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 --- BUILD.gn | 28 +- 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 +++ third_party/harfbuzz/BUILD.gn | 1 - tools/shape/SkShaper.h | 47 -- tools/shape/SkShaper_harfbuzz.cpp | 728 ---------------------------- tools/shape/SkShaper_primitive.cpp | 76 --- tools/shape/using_skia_and_harfbuzz.cpp | 222 --------- tools/using_skia_and_harfbuzz.cpp | 222 +++++++++ 11 files changed, 1117 insertions(+), 1098 deletions(-) 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 delete mode 100644 tools/shape/SkShaper.h delete mode 100644 tools/shape/SkShaper_harfbuzz.cpp delete mode 100644 tools/shape/SkShaper_primitive.cpp delete mode 100644 tools/shape/using_skia_and_harfbuzz.cpp create mode 100644 tools/using_skia_and_harfbuzz.cpp diff --git a/BUILD.gn b/BUILD.gn index d0c6755555..d871e782b5 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1474,7 +1474,7 @@ if (skia_enable_tools) { "src/utils/SkLuaCanvas.cpp", ] deps = [ - ":skshaper", + "modules/skshaper", "//third_party/lua", ] } @@ -1535,11 +1535,11 @@ if (skia_enable_tools) { ":experimental_svg_model", ":flags", ":gm", - ":skshaper", ":tool_utils", ":views", ":xml", "modules/sksg:samples", + "modules/skshaper", ] if (skia_use_lua) { @@ -1629,33 +1629,13 @@ if (skia_enable_tools) { } } - test_lib("skshaper") { - public_include_dirs = [ "tools/shape" ] - deps = [ - ":skia", - ] - - if (target_cpu == "wasm") { - sources = [ - "tools/shape/SkShaper_primitive.cpp", - ] - } else { - sources = [ - "tools/shape/SkShaper_harfbuzz.cpp", - ] - deps += [ - "//third_party/harfbuzz", - "//third_party/icu", - ] - } - } test_app("sktexttopdf") { sources = [ - "tools/shape/using_skia_and_harfbuzz.cpp", + "tools/using_skia_and_harfbuzz.cpp", ] deps = [ ":skia", - ":skshaper", + "modules/skshaper", ] } 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; +} diff --git a/third_party/harfbuzz/BUILD.gn b/third_party/harfbuzz/BUILD.gn index 9ac98350a2..851d3fb5ac 100644 --- a/third_party/harfbuzz/BUILD.gn +++ b/third_party/harfbuzz/BUILD.gn @@ -61,5 +61,4 @@ third_party("harfbuzz") { sources += [ "../externals/harfbuzz/src/hb-coretext.cc" ] defines += [ "HAVE_CORETEXT" ] } - testonly = true } diff --git a/tools/shape/SkShaper.h b/tools/shape/SkShaper.h deleted file mode 100644 index 190a4d834e..0000000000 --- a/tools/shape/SkShaper.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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/tools/shape/SkShaper_harfbuzz.cpp b/tools/shape/SkShaper_harfbuzz.cpp deleted file mode 100644 index 40414d9a79..0000000000 --- a/tools/shape/SkShaper_harfbuzz.cpp +++ /dev/null @@ -1,728 +0,0 @@ -/* - * 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/tools/shape/SkShaper_primitive.cpp b/tools/shape/SkShaper_primitive.cpp deleted file mode 100644 index 06a8bec41c..0000000000 --- a/tools/shape/SkShaper_primitive.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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; -} diff --git a/tools/shape/using_skia_and_harfbuzz.cpp b/tools/shape/using_skia_and_harfbuzz.cpp deleted file mode 100644 index 5a74866794..0000000000 --- a/tools/shape/using_skia_and_harfbuzz.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2016 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -// This sample progam demonstrates how to use Skia and HarfBuzz to -// produce a PDF file from UTF-8 text in stdin. - -#include -#include -#include -#include -#include -#include -#include - -#include "SkCanvas.h" -#include "SkDocument.h" -#include "SkShaper.h" -#include "SkStream.h" -#include "SkTextBlob.h" -#include "SkTypeface.h" - -// Options ///////////////////////////////////////////////////////////////////// - -struct BaseOption { - std::string selector; - std::string description; - virtual void set(std::string _value) = 0; - virtual std::string valueToString() = 0; - - BaseOption(std::string _selector, std::string _description) - : selector(_selector), description(_description) {} - - virtual ~BaseOption() {} - - static void Init(const std::vector &, int argc, char **argv); -}; - -template -struct Option : BaseOption { - T value; - Option(std::string selector, std::string description, T defaultValue) - : BaseOption(selector, description), value(defaultValue) {} -}; - -void BaseOption::Init(const std::vector &option_list, - int argc, char **argv) { - std::map options; - for (BaseOption *opt : option_list) { - options[opt->selector] = opt; - } - for (int i = 1; i < argc; i++) { - std::string option_selector(argv[i]); - auto it = options.find(option_selector); - if (it != options.end()) { - if (i >= argc) { - break; - } - const char *option_value = argv[i + 1]; - it->second->set(option_value); - i++; - } else { - printf("Ignoring unrecognized option: %s.\n", argv[i]); - printf("Usage: %s {option value}\n", argv[0]); - printf("\tTakes text from stdin and produces pdf file.\n"); - printf("Supported options:\n"); - for (BaseOption *opt : option_list) { - printf("\t%s\t%s (%s)\n", opt->selector.c_str(), - opt->description.c_str(), opt->valueToString().c_str()); - } - exit(-1); - } - } -} - -struct DoubleOption : Option { - virtual void set(std::string _value) { value = atof(_value.c_str()); } - virtual std::string valueToString() { - std::ostringstream stm; - stm << value; - return stm.str(); - } - DoubleOption(std::string selector, - std::string description, - double defaultValue) - : Option(selector, description, defaultValue) {} -}; - -struct StringOption : Option { - virtual void set(std::string _value) { value = _value; } - virtual std::string valueToString() { return value; } - StringOption(std::string selector, - std::string description, - std::string defaultValue) - : Option(selector, description, defaultValue) {} -}; - -// Config ////////////////////////////////////////////////////////////////////// - -struct Config { - DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f); - DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f); - StringOption title = StringOption("-t", "PDF title", "---"); - StringOption author = StringOption("-a", "PDF author", "---"); - StringOption subject = StringOption("-k", "PDF subject", "---"); - StringOption keywords = StringOption("-c", "PDF keywords", "---"); - StringOption creator = StringOption("-t", "PDF creator", "---"); - StringOption font_file = StringOption("-f", ".ttf font file", ""); - DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f); - DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f); - DoubleOption line_spacing_ratio = - DoubleOption("-h", "Line spacing ratio", 0.25f); - StringOption output_file_name = - StringOption("-o", ".pdf output file name", "out-skiahf.pdf"); - - Config(int argc, char **argv) { - BaseOption::Init(std::vector{ - &page_width, &page_height, &title, &author, &subject, - &keywords, &creator, &font_file, &font_size, &left_margin, - &line_spacing_ratio, &output_file_name}, argc, argv); - } -}; - -// Placement /////////////////////////////////////////////////////////////////// - -class Placement { -public: - Placement(const Config* conf, SkDocument *doc) - : config(conf), document(doc), pageCanvas(nullptr) { - white_paint.setColor(SK_ColorWHITE); - glyph_paint.setColor(SK_ColorBLACK); - glyph_paint.setFlags(SkPaint::kAntiAlias_Flag | - SkPaint::kSubpixelText_Flag); - glyph_paint.setTextSize(SkDoubleToScalar(config->font_size.value)); - } - - void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) { - SkTextBlobBuilder textBlobBuilder; - SkPoint endPoint = shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, true, - SkPoint{0, 0}, - config->page_width.value - 2*config->left_margin.value); - sk_sp blob = textBlobBuilder.make(); - // If we don't have a page, or if we're not at the start of the page and the blob won't fit - if (!pageCanvas || - (current_y > config->line_spacing_ratio.value * config->font_size.value && - current_y + endPoint.y() > config->page_height.value) - ) { - if (pageCanvas) { - document->endPage(); - } - pageCanvas = document->beginPage( - SkDoubleToScalar(config->page_width.value), - SkDoubleToScalar(config->page_height.value)); - pageCanvas->drawPaint(white_paint); - current_x = config->left_margin.value; - current_y = config->line_spacing_ratio.value * config->font_size.value; - } - pageCanvas->drawTextBlob( - blob.get(), SkDoubleToScalar(current_x), - SkDoubleToScalar(current_y), glyph_paint); - // Advance to the next line. - current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value; - } - -private: - const Config* config; - SkDocument *document; - SkCanvas *pageCanvas; - SkPaint white_paint; - SkPaint glyph_paint; - double current_x; - double current_y; -}; - -//////////////////////////////////////////////////////////////////////////////// - -static sk_sp MakePDFDocument(const Config &config, SkWStream *wStream) { - SkDocument::PDFMetadata pdf_info; - pdf_info.fTitle = config.title.value.c_str(); - pdf_info.fAuthor = config.author.value.c_str(); - pdf_info.fSubject = config.subject.value.c_str(); - pdf_info.fKeywords = config.keywords.value.c_str(); - pdf_info.fCreator = config.creator.value.c_str(); - #if 0 - SkTime::DateTime now; - SkTime::GetDateTime(&now); - pdf_info.fCreation.fEnabled = true; - pdf_info.fCreation.fDateTime = now; - pdf_info.fModified.fEnabled = true; - pdf_info.fModified.fDateTime = now; - pdf_info.fPDFA = true; - #endif - return SkDocument::MakePDF(wStream, pdf_info); -} - -int main(int argc, char **argv) { - Config config(argc, argv); - SkFILEWStream wStream(config.output_file_name.value.c_str()); - sk_sp doc = MakePDFDocument(config, &wStream); - assert(doc); - Placement placement(&config, doc.get()); - - const std::string &font_file = config.font_file.value; - sk_sp typeface; - if (font_file.size() > 0) { - typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */); - } - SkShaper shaper(typeface); - assert(shaper.good()); - //SkString line("This is هذا هو الخط a line."); - //SkString line("⁧This is a line هذا هو الخط.⁩"); - for (std::string line; std::getline(std::cin, line);) { - placement.WriteLine(shaper, line.c_str(), line.size()); - } - - doc->close(); - wStream.flush(); - return 0; -} diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp new file mode 100644 index 0000000000..5a74866794 --- /dev/null +++ b/tools/using_skia_and_harfbuzz.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// This sample progam demonstrates how to use Skia and HarfBuzz to +// produce a PDF file from UTF-8 text in stdin. + +#include +#include +#include +#include +#include +#include +#include + +#include "SkCanvas.h" +#include "SkDocument.h" +#include "SkShaper.h" +#include "SkStream.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" + +// Options ///////////////////////////////////////////////////////////////////// + +struct BaseOption { + std::string selector; + std::string description; + virtual void set(std::string _value) = 0; + virtual std::string valueToString() = 0; + + BaseOption(std::string _selector, std::string _description) + : selector(_selector), description(_description) {} + + virtual ~BaseOption() {} + + static void Init(const std::vector &, int argc, char **argv); +}; + +template +struct Option : BaseOption { + T value; + Option(std::string selector, std::string description, T defaultValue) + : BaseOption(selector, description), value(defaultValue) {} +}; + +void BaseOption::Init(const std::vector &option_list, + int argc, char **argv) { + std::map options; + for (BaseOption *opt : option_list) { + options[opt->selector] = opt; + } + for (int i = 1; i < argc; i++) { + std::string option_selector(argv[i]); + auto it = options.find(option_selector); + if (it != options.end()) { + if (i >= argc) { + break; + } + const char *option_value = argv[i + 1]; + it->second->set(option_value); + i++; + } else { + printf("Ignoring unrecognized option: %s.\n", argv[i]); + printf("Usage: %s {option value}\n", argv[0]); + printf("\tTakes text from stdin and produces pdf file.\n"); + printf("Supported options:\n"); + for (BaseOption *opt : option_list) { + printf("\t%s\t%s (%s)\n", opt->selector.c_str(), + opt->description.c_str(), opt->valueToString().c_str()); + } + exit(-1); + } + } +} + +struct DoubleOption : Option { + virtual void set(std::string _value) { value = atof(_value.c_str()); } + virtual std::string valueToString() { + std::ostringstream stm; + stm << value; + return stm.str(); + } + DoubleOption(std::string selector, + std::string description, + double defaultValue) + : Option(selector, description, defaultValue) {} +}; + +struct StringOption : Option { + virtual void set(std::string _value) { value = _value; } + virtual std::string valueToString() { return value; } + StringOption(std::string selector, + std::string description, + std::string defaultValue) + : Option(selector, description, defaultValue) {} +}; + +// Config ////////////////////////////////////////////////////////////////////// + +struct Config { + DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f); + DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f); + StringOption title = StringOption("-t", "PDF title", "---"); + StringOption author = StringOption("-a", "PDF author", "---"); + StringOption subject = StringOption("-k", "PDF subject", "---"); + StringOption keywords = StringOption("-c", "PDF keywords", "---"); + StringOption creator = StringOption("-t", "PDF creator", "---"); + StringOption font_file = StringOption("-f", ".ttf font file", ""); + DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f); + DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f); + DoubleOption line_spacing_ratio = + DoubleOption("-h", "Line spacing ratio", 0.25f); + StringOption output_file_name = + StringOption("-o", ".pdf output file name", "out-skiahf.pdf"); + + Config(int argc, char **argv) { + BaseOption::Init(std::vector{ + &page_width, &page_height, &title, &author, &subject, + &keywords, &creator, &font_file, &font_size, &left_margin, + &line_spacing_ratio, &output_file_name}, argc, argv); + } +}; + +// Placement /////////////////////////////////////////////////////////////////// + +class Placement { +public: + Placement(const Config* conf, SkDocument *doc) + : config(conf), document(doc), pageCanvas(nullptr) { + white_paint.setColor(SK_ColorWHITE); + glyph_paint.setColor(SK_ColorBLACK); + glyph_paint.setFlags(SkPaint::kAntiAlias_Flag | + SkPaint::kSubpixelText_Flag); + glyph_paint.setTextSize(SkDoubleToScalar(config->font_size.value)); + } + + void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) { + SkTextBlobBuilder textBlobBuilder; + SkPoint endPoint = shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, true, + SkPoint{0, 0}, + config->page_width.value - 2*config->left_margin.value); + sk_sp blob = textBlobBuilder.make(); + // If we don't have a page, or if we're not at the start of the page and the blob won't fit + if (!pageCanvas || + (current_y > config->line_spacing_ratio.value * config->font_size.value && + current_y + endPoint.y() > config->page_height.value) + ) { + if (pageCanvas) { + document->endPage(); + } + pageCanvas = document->beginPage( + SkDoubleToScalar(config->page_width.value), + SkDoubleToScalar(config->page_height.value)); + pageCanvas->drawPaint(white_paint); + current_x = config->left_margin.value; + current_y = config->line_spacing_ratio.value * config->font_size.value; + } + pageCanvas->drawTextBlob( + blob.get(), SkDoubleToScalar(current_x), + SkDoubleToScalar(current_y), glyph_paint); + // Advance to the next line. + current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value; + } + +private: + const Config* config; + SkDocument *document; + SkCanvas *pageCanvas; + SkPaint white_paint; + SkPaint glyph_paint; + double current_x; + double current_y; +}; + +//////////////////////////////////////////////////////////////////////////////// + +static sk_sp MakePDFDocument(const Config &config, SkWStream *wStream) { + SkDocument::PDFMetadata pdf_info; + pdf_info.fTitle = config.title.value.c_str(); + pdf_info.fAuthor = config.author.value.c_str(); + pdf_info.fSubject = config.subject.value.c_str(); + pdf_info.fKeywords = config.keywords.value.c_str(); + pdf_info.fCreator = config.creator.value.c_str(); + #if 0 + SkTime::DateTime now; + SkTime::GetDateTime(&now); + pdf_info.fCreation.fEnabled = true; + pdf_info.fCreation.fDateTime = now; + pdf_info.fModified.fEnabled = true; + pdf_info.fModified.fDateTime = now; + pdf_info.fPDFA = true; + #endif + return SkDocument::MakePDF(wStream, pdf_info); +} + +int main(int argc, char **argv) { + Config config(argc, argv); + SkFILEWStream wStream(config.output_file_name.value.c_str()); + sk_sp doc = MakePDFDocument(config, &wStream); + assert(doc); + Placement placement(&config, doc.get()); + + const std::string &font_file = config.font_file.value; + sk_sp typeface; + if (font_file.size() > 0) { + typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */); + } + SkShaper shaper(typeface); + assert(shaper.good()); + //SkString line("This is هذا هو الخط a line."); + //SkString line("⁧This is a line هذا هو الخط.⁩"); + for (std::string line; std::getline(std::cin, line);) { + placement.WriteLine(shaper, line.c_str(), line.size()); + } + + doc->close(); + wStream.flush(); + return 0; +} -- cgit v1.2.3