aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/shape
diff options
context:
space:
mode:
authorGravatar Ben Wagner <bungeman@google.com>2017-08-30 13:56:19 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-08-31 14:18:31 +0000
commita25fbef56a4e70192b0b322b6d87b78839fab0e5 (patch)
tree6ee0cc9f28a6a2c93cc742d01869e3190c669cb1 /tools/shape
parentf95b17524628bd0caad7c7e26417be985905944b (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')
-rw-r--r--tools/shape/SkShaper.h46
-rw-r--r--tools/shape/SkShaper_harfbuzz.cpp190
-rw-r--r--tools/shape/SkShaper_primitive.cpp70
-rw-r--r--tools/shape/using_skia_and_harfbuzz.cpp218
4 files changed, 524 insertions, 0 deletions
diff --git a/tools/shape/SkShaper.h b/tools/shape/SkShaper.h
new file mode 100644
index 0000000000..a2a301e792
--- /dev/null
+++ b/tools/shape/SkShaper.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <memory>
+
+#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<SkTypeface> face);
+ ~SkShaper();
+
+ bool good() const;
+ SkScalar shape(SkTextBlobBuilder* dest,
+ const SkPaint& srcPaint,
+ const char* utf8text,
+ size_t textBytes,
+ bool leftToRight,
+ SkPoint point) const;
+
+private:
+ SkShaper(const SkShaper&) = delete;
+ SkShaper& operator=(const SkShaper&) = delete;
+
+ struct Impl;
+ std::unique_ptr<Impl> fImpl;
+};
+
+#endif // SkShaper_DEFINED
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;
+}
diff --git a/tools/shape/SkShaper_primitive.cpp b/tools/shape/SkShaper_primitive.cpp
new file mode 100644
index 0000000000..750c51621e
--- /dev/null
+++ b/tools/shape/SkShaper_primitive.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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<SkTypeface> fTypeface;
+};
+
+SkShaper::SkShaper(sk_sp<SkTypeface> 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;
+}
+
+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);
+ int glyphCount = paint.countText(utf8text, textBytes);
+ if (glyphCount <= 0) {
+ return 0;
+ }
+ SkRect bounds;
+ (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;
+ }
+ return (SkScalar)x;
+}
diff --git a/tools/shape/using_skia_and_harfbuzz.cpp b/tools/shape/using_skia_and_harfbuzz.cpp
new file mode 100644
index 0000000000..3f1515fb1d
--- /dev/null
+++ b/tools/shape/using_skia_and_harfbuzz.cpp
@@ -0,0 +1,218 @@
+/*
+ * 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 <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#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<BaseOption*> &, int argc, char **argv);
+};
+
+template <class T>
+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<BaseOption*> &option_list,
+ int argc, char **argv) {
+ std::map<std::string, BaseOption *> 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<double> {
+ 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<double>(selector, description, defaultValue) {}
+};
+
+struct StringOption : Option<std::string> {
+ 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<std::string>(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", 1.5f);
+ StringOption output_file_name =
+ StringOption("-o", ".pdf output file name", "out-skiahf.pdf");
+
+ Config(int argc, char **argv) {
+ BaseOption::Init(std::vector<BaseOption*>{
+ &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) {
+ if (!pageCanvas || current_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;
+ }
+ SkTextBlobBuilder textBlobBuilder;
+ 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),
+ SkDoubleToScalar(current_y), glyph_paint);
+ // Advance to the next line.
+ current_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<SkDocument> 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();
+ bool pdfa = false;
+ #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;
+ pdfa = true;
+ #endif
+ return SkDocument::MakePDF(wStream, SK_ScalarDefaultRasterDPI, pdf_info,
+ nullptr, pdfa);
+}
+
+int main(int argc, char **argv) {
+ Config config(argc, argv);
+ SkFILEWStream wStream(config.output_file_name.value.c_str());
+ sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream);
+ assert(doc);
+ Placement placement(&config, doc.get());
+
+ const std::string &font_file = config.font_file.value;
+ sk_sp<SkTypeface> 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.");
+ for (std::string line; std::getline(std::cin, line);) {
+ placement.WriteLine(shaper, line.c_str(), line.size());
+ }
+
+ doc->close();
+ wStream.flush();
+ return 0;
+}