| /* |
| * 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; |
| } |