diff options
author | 2017-08-30 13:56:19 -0400 | |
---|---|---|
committer | 2017-08-31 14:18:31 +0000 | |
commit | a25fbef56a4e70192b0b322b6d87b78839fab0e5 (patch) | |
tree | 6ee0cc9f28a6a2c93cc742d01869e3190c669cb1 /tools/shape/SkShaper_harfbuzz.cpp | |
parent | f95b17524628bd0caad7c7e26417be985905944b (diff) |
Add bidi to SkShaper.
Change-Id: Iaab9a1d5091a3b157a582b7e8c3261a6c3283ffc
Reviewed-on: https://skia-review.googlesource.com/40778
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
Diffstat (limited to 'tools/shape/SkShaper_harfbuzz.cpp')
-rw-r--r-- | tools/shape/SkShaper_harfbuzz.cpp | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/tools/shape/SkShaper_harfbuzz.cpp b/tools/shape/SkShaper_harfbuzz.cpp new file mode 100644 index 0000000000..3947406b85 --- /dev/null +++ b/tools/shape/SkShaper_harfbuzz.cpp @@ -0,0 +1,190 @@ +/* + * 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 <hb-ot.h> +#include <unicode/stringpiece.h> +#include <unicode/ubidi.h> +#include <unicode/unistr.h> + +#include "SkShaper.h" +#include "SkStream.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" +#include "SkUtils.h" + +static const int FONT_SIZE_SCALE = 512; + +namespace { +template <class T, void(*P)(T*)> using resource = std::unique_ptr<T, SkFunctionWrapper<void, T, P>>; +using HBBlob = resource<hb_blob_t , hb_blob_destroy >; +using HBFace = resource<hb_face_t , hb_face_destroy >; +using HBFont = resource<hb_font_t , hb_font_destroy >; +using HBBuffer = resource<hb_buffer_t, hb_buffer_destroy>; +using ICUBiDi = resource<UBiDi , ubidi_close >; + +HBBlob stream_to_blob(std::unique_ptr<SkStreamAsset> 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; +} +} // namespace + +struct SkShaper::Impl { + HBFont fHarfBuzzFont; + HBBuffer fBuffer; + sk_sp<SkTypeface> fTypeface; +}; + +SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl) { + fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault(); + int index; + HBBlob blob(stream_to_blob(std::unique_ptr<SkStreamAsset>( + fImpl->fTypeface->openStream(&index)))); + HBFace face(hb_face_create(blob.get(), (unsigned)index)); + SkASSERT(face); + if (!face) { + return; + } + hb_face_set_index(face.get(), (unsigned)index); + hb_face_set_upem(face.get(), fImpl->fTypeface->getUnitsPerEm()); + + fImpl->fHarfBuzzFont.reset(hb_font_create(face.get())); + SkASSERT(fImpl->fHarfBuzzFont); + hb_font_set_scale(fImpl->fHarfBuzzFont.get(), FONT_SIZE_SCALE, FONT_SIZE_SCALE); + hb_ot_font_set_funcs(fImpl->fHarfBuzzFont.get()); + + fImpl->fBuffer.reset(hb_buffer_create()); +} + +SkShaper::~SkShaper() {} + +bool SkShaper::good() const { return fImpl->fHarfBuzzFont != nullptr; } + +SkScalar SkShaper::shape(SkTextBlobBuilder* builder, + const SkPaint& srcPaint, + const char* utf8text, + size_t textBytes, + bool leftToRight, + SkPoint point) const { + SkASSERT(builder); + UBiDiLevel bidiLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL; + //hb_script_t script = ... + UErrorCode status = U_ZERO_ERROR; + double x = point.x(); + double y = point.y(); + + // This function only accepts utf8. + // ubidi only accepts utf16 (though internally it basically works on utf32 chars). + // Internally, harfbuzz is all utf32, but always makes a copy. + + if (!SkTFitsIn<int32_t>(textBytes)) { + SkDebugf("Bidi error: text too long"); + return (SkScalar)x; + } + icu::UnicodeString utf16 = icu::UnicodeString::fromUTF8(icu::StringPiece(utf8text, textBytes)); + + ICUBiDi bidi(ubidi_openSized(utf16.length(), 0, &status)); + if (U_FAILURE(status)) { + SkDebugf("Bidi error: %s", u_errorName(status)); + return (SkScalar)x; + } + SkASSERT(bidi); + + ubidi_setPara(bidi.get(), utf16.getBuffer(), utf16.length(), bidiLevel, nullptr, &status); + if (U_FAILURE(status)) { + SkDebugf("Bidi error: %s", u_errorName(status)); + return (SkScalar)x; + } + + int32_t runCount = ubidi_countRuns(bidi.get(), &status); + if (U_FAILURE(status)) { + SkDebugf("Bidi error: %s", u_errorName(status)); + return (SkScalar)x; + } + + for (int32_t i = 0; i < runCount; ++i) { + int32_t start; + int32_t length; + UBiDiDirection direction = ubidi_getVisualRun(bidi.get(), i, &start, &length); + + SkPaint paint(srcPaint); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setTypeface(fImpl->fTypeface); + + hb_buffer_t* buffer = fImpl->fBuffer.get(); + SkAutoTCallVProc<hb_buffer_t, hb_buffer_clear_contents> autoClearBuffer(buffer); + + // The difficulty here is the cluster mapping. + // If the hb_buffer is created with utf16, clusters will be pointing to the utf16 indexes, + // but the SkTextBlob can only take utf8 and utf8 cluster indexes. + // Instead of updating each cluster index, create the hb_buffer from the utf8. + // TODO: this is horribly inefficient. + const char* utf8textStart = utf8text; + const UChar* utf16Start = utf16.getBuffer(); + while (utf16Start < utf16.getBuffer() + start) { + SkUTF16_NextUnichar(&utf16Start); + SkUTF8_NextUnichar(&utf8textStart); + } + const char* utf8textEnd = utf8textStart; + const UChar* utf16End = utf16Start; + while (utf16End < utf16.getBuffer() + start + length) { + SkUTF16_NextUnichar(&utf16End); + SkUTF8_NextUnichar(&utf8textEnd); + } + size_t utf8runLength = utf8textEnd - utf8textStart; + if (!SkTFitsIn<int>(utf8runLength)) { + SkDebugf("Shaping error: utf8 too long"); + return (SkScalar)x; + } + hb_buffer_add_utf8(buffer, utf8textStart, utf8runLength, 0, -1); + hb_buffer_guess_segment_properties(buffer); + //hb_buffer_set_script(buffer, script); + hb_buffer_set_direction(buffer, direction ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); + hb_shape(fImpl->fHarfBuzzFont.get(), buffer, nullptr, 0); + unsigned len = hb_buffer_get_length(buffer); + if (len == 0) { + continue; + } + + 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<int>(len)) { + SkDebugf("Shaping error: too many glyphs"); + return (SkScalar)x; + } + auto runBuffer = builder->allocRunTextPos(paint, len, utf8runLength, SkString()); + memcpy(runBuffer.utf8text, utf8textStart, utf8runLength); + + double textSizeY = paint.getTextSize() / (double)FONT_SIZE_SCALE; + double textSizeX = textSizeY * paint.getTextScaleX(); + + for (unsigned i = 0; i < len; i++) { + runBuffer.glyphs[i] = info[i].codepoint; + runBuffer.clusters[i] = info[i].cluster; + reinterpret_cast<SkPoint*>(runBuffer.pos)[i] = + SkPoint::Make(SkDoubleToScalar(x + pos[i].x_offset * textSizeX), + SkDoubleToScalar(y - pos[i].y_offset * textSizeY)); + x += pos[i].x_advance * textSizeX; + y += pos[i].y_advance * textSizeY; + } + } + return (SkScalar)x; +} |