diff --git a/BUILD.gn b/BUILD.gn index 98bfdc5ba3..0e4c0715f6 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1389,19 +1389,20 @@ if (skia_enable_tools) { if (!is_ios && !is_win) { test_app("sktexttopdf-hb") { sources = [ - "tools/SkShaper_harfbuzz.cpp", - "tools/using_skia_and_harfbuzz.cpp", + "tools/shape/SkShaper_harfbuzz.cpp", + "tools/shape/using_skia_and_harfbuzz.cpp", ] deps = [ ":skia", "//third_party/harfbuzz", + "//third_party/icu", ] } } test_app("sktexttopdf") { sources = [ - "tools/SkShaper_primitive.cpp", - "tools/using_skia_and_harfbuzz.cpp", + "tools/shape/SkShaper_primitive.cpp", + "tools/shape/using_skia_and_harfbuzz.cpp", ] deps = [ ":skia", diff --git a/third_party/icu/BUILD.gn b/third_party/icu/BUILD.gn index 1d54c46edd..0a1683e81f 100644 --- a/third_party/icu/BUILD.gn +++ b/third_party/icu/BUILD.gn @@ -12,10 +12,12 @@ import("../third_party.gni") if (skia_use_system_icu) { system("icu") { libs = [ "icuuc" ] + public_defines = [ "U_USING_ICU_NAMESPACE=0" ] } } else { third_party("icu") { public_include_dirs = [ "../externals/icu/source/common" ] + public_defines = [ "U_USING_ICU_NAMESPACE=0" ] configs -= [ "//gn:no_rtti" ] if (!is_win) { libs = [ "dl" ] diff --git a/tools/SkShaper_harfbuzz.cpp b/tools/SkShaper_harfbuzz.cpp deleted file mode 100644 index 34fc712829..0000000000 --- a/tools/SkShaper_harfbuzz.cpp +++ /dev/null @@ -1,129 +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 "SkShaper.h" -#include "SkStream.h" -#include "SkTextBlob.h" -#include "SkTypeface.h" - -static const int FONT_SIZE_SCALE = 512; - -namespace { -struct HBFBlobDel { - void operator()(hb_blob_t* b) { hb_blob_destroy(b); } -}; - -std::unique_ptr stream_to_blob(std::unique_ptr asset) { - size_t size = asset->getLength(); - std::unique_ptr 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 { - struct HBFontDel { - void operator()(hb_font_t* f) { hb_font_destroy(f); } - }; - std::unique_ptr fHarfBuzzFont; - struct HBBufDel { - void operator()(hb_buffer_t* b) { hb_buffer_destroy(b); } - }; - std::unique_ptr fBuffer; - sk_sp fTypeface; -}; - -SkShaper::SkShaper(sk_sp tf) : fImpl(new Impl) { - fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault(); - int index; - std::unique_ptr blob( - stream_to_blob(std::unique_ptr( - fImpl->fTypeface->openStream(&index)))); - struct HBFaceDel { - void operator()(hb_face_t* f) { hb_face_destroy(f); } - }; - std::unique_ptr 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, - SkPoint point) const { - SkPaint paint(srcPaint); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - paint.setTypeface(fImpl->fTypeface); - - SkASSERT(builder); - hb_buffer_t* buffer = fImpl->fBuffer.get(); - hb_buffer_add_utf8(buffer, utf8text, -1, 0, -1); - hb_buffer_guess_segment_properties(buffer); - hb_shape(fImpl->fHarfBuzzFont.get(), buffer, nullptr, 0); - unsigned len = hb_buffer_get_length(buffer); - if (len == 0) { - hb_buffer_clear_contents(buffer); - return 0; - } - - hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, nullptr); - hb_glyph_position_t* pos = - hb_buffer_get_glyph_positions(buffer, nullptr); - auto runBuffer = builder->allocRunTextPos( - paint, SkToInt(len), SkToInt(textBytes), SkString()); - memcpy(runBuffer.utf8text, utf8text, textBytes); - - double x = point.x(); - double y = point.y(); - - 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(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; - } - - hb_buffer_clear_contents(buffer); - return (SkScalar)x; -} diff --git a/tools/SkShaper.h b/tools/shape/SkShaper.h similarity index 96% rename from tools/SkShaper.h rename to tools/shape/SkShaper.h index bc78be7d54..a2a301e792 100644 --- a/tools/SkShaper.h +++ b/tools/shape/SkShaper.h @@ -32,6 +32,7 @@ public: const SkPaint& srcPaint, const char* utf8text, size_t textBytes, + bool leftToRight, SkPoint point) const; private: 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 +#include +#include +#include + +#include "SkShaper.h" +#include "SkStream.h" +#include "SkTextBlob.h" +#include "SkTypeface.h" +#include "SkUtils.h" + +static const int FONT_SIZE_SCALE = 512; + +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; +} +} // namespace + +struct SkShaper::Impl { + HBFont fHarfBuzzFont; + HBBuffer fBuffer; + sk_sp fTypeface; +}; + +SkShaper::SkShaper(sk_sp tf) : fImpl(new Impl) { + fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault(); + int index; + HBBlob blob(stream_to_blob(std::unique_ptr( + 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(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 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(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(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(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; +} diff --git a/tools/SkShaper_primitive.cpp b/tools/shape/SkShaper_primitive.cpp similarity index 96% rename from tools/SkShaper_primitive.cpp rename to tools/shape/SkShaper_primitive.cpp index b165285287..750c51621e 100644 --- a/tools/SkShaper_primitive.cpp +++ b/tools/shape/SkShaper_primitive.cpp @@ -33,7 +33,10 @@ SkScalar SkShaper::shape(SkTextBlobBuilder* builder, const SkPaint& srcPaint, const char* utf8text, size_t textBytes, + bool leftToRight, SkPoint point) const { + sk_ignore_unused_variable(leftToRight); + SkPaint paint(srcPaint); paint.setTypeface(fImpl->fTypeface); paint.setTextEncoding(SkPaint::kUTF8_TextEncoding); diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/shape/using_skia_and_harfbuzz.cpp similarity index 98% rename from tools/using_skia_and_harfbuzz.cpp rename to tools/shape/using_skia_and_harfbuzz.cpp index d405c71313..3f1515fb1d 100644 --- a/tools/using_skia_and_harfbuzz.cpp +++ b/tools/shape/using_skia_and_harfbuzz.cpp @@ -150,7 +150,7 @@ public: current_y = config->line_spacing_ratio.value * config->font_size.value; } SkTextBlobBuilder textBlobBuilder; - shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, SkPoint{0, 0}); + shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, true, SkPoint{0, 0}); sk_sp blob = textBlobBuilder.make(); pageCanvas->drawTextBlob( blob.get(), SkDoubleToScalar(current_x), @@ -207,10 +207,12 @@ int main(int argc, char **argv) { } SkShaper shaper(typeface); assert(shaper.good()); + //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; }