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>
This commit is contained in:
parent
f95b175246
commit
a25fbef56a
9
BUILD.gn
9
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",
|
||||
|
2
third_party/icu/BUILD.gn
vendored
2
third_party/icu/BUILD.gn
vendored
@ -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" ]
|
||||
|
@ -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 <hb-ot.h>
|
||||
|
||||
#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<hb_blob_t, HBFBlobDel> stream_to_blob(std::unique_ptr<SkStreamAsset> asset) {
|
||||
size_t size = asset->getLength();
|
||||
std::unique_ptr<hb_blob_t, HBFBlobDel> 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<hb_font_t, HBFontDel> fHarfBuzzFont;
|
||||
struct HBBufDel {
|
||||
void operator()(hb_buffer_t* b) { hb_buffer_destroy(b); }
|
||||
};
|
||||
std::unique_ptr<hb_buffer_t, HBBufDel> fBuffer;
|
||||
sk_sp<SkTypeface> fTypeface;
|
||||
};
|
||||
|
||||
SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl) {
|
||||
fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault();
|
||||
int index;
|
||||
std::unique_ptr<hb_blob_t, HBFBlobDel> blob(
|
||||
stream_to_blob(std::unique_ptr<SkStreamAsset>(
|
||||
fImpl->fTypeface->openStream(&index))));
|
||||
struct HBFaceDel {
|
||||
void operator()(hb_face_t* f) { hb_face_destroy(f); }
|
||||
};
|
||||
std::unique_ptr<hb_face_t, HBFaceDel> 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<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;
|
||||
}
|
||||
|
||||
hb_buffer_clear_contents(buffer);
|
||||
return (SkScalar)x;
|
||||
}
|
@ -32,6 +32,7 @@ public:
|
||||
const SkPaint& srcPaint,
|
||||
const char* utf8text,
|
||||
size_t textBytes,
|
||||
bool leftToRight,
|
||||
SkPoint point) const;
|
||||
|
||||
private:
|
190
tools/shape/SkShaper_harfbuzz.cpp
Normal file
190
tools/shape/SkShaper_harfbuzz.cpp
Normal file
@ -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;
|
||||
}
|
@ -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);
|
@ -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<const SkTextBlob> 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user