From 8a74f132398f107963fe1b27fb54b63a008e67dd Mon Sep 17 00:00:00 2001 From: halcanary Date: Mon, 11 Jul 2016 14:30:39 -0700 Subject: [PATCH] tools/SkShaper: factor shaping out of using_skia_and_harfbuzz Make a clearer demarcation between shaping and layout. also: refactor using_skia_and_harfbuzz; stop leaking like a sieve. Review-Url: https://codereview.chromium.org/2138133002 --- gyp/tools.gyp | 5 +- tools/SkShaper.cpp | 108 ++++++++ tools/SkShaper.h | 43 +++ tools/using_skia_and_harfbuzz.cpp | 438 +++++++++++------------------- 4 files changed, 312 insertions(+), 282 deletions(-) create mode 100644 tools/SkShaper.cpp create mode 100644 tools/SkShaper.h diff --git a/gyp/tools.gyp b/gyp/tools.gyp index e2f0bd89e2..1904d09201 100644 --- a/gyp/tools.gyp +++ b/gyp/tools.gyp @@ -445,7 +445,10 @@ { 'target_name': 'using_skia_and_harfbuzz', 'type': 'executable', - 'sources': [ '../tools/using_skia_and_harfbuzz.cpp' ], + 'sources': [ + '../tools/using_skia_and_harfbuzz.cpp', + '../tools/SkShaper.cpp', + ], 'dependencies': [ 'skia_lib.gyp:skia_lib', 'pdf.gyp:pdf', diff --git a/tools/SkShaper.cpp b/tools/SkShaper.cpp new file mode 100644 index 0000000000..377f07c109 --- /dev/null +++ b/tools/SkShaper.cpp @@ -0,0 +1,108 @@ +/* + * 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; + +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 asset(fImpl->fTypeface->openStream(&index)); + size_t size = asset->getLength(); + SkAutoMalloc autoMalloc(size); // TODO(halcanary): Avoid this malloc+copy. + asset->read(autoMalloc.get(), size); + asset = nullptr; + void* ptr = autoMalloc.get(); + hb_blob_t* blob = hb_blob_create((char*)autoMalloc.release(), size, + HB_MEMORY_MODE_READONLY, ptr, sk_free); + SkASSERT(blob); + hb_blob_make_immutable(blob); + + struct HBFaceDel { + void operator()(hb_face_t* f) { hb_face_destroy(f); } + }; + std::unique_ptr face(hb_face_create(blob, (unsigned)index)); + hb_blob_destroy(blob); + 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, NULL); + hb_glyph_position_t* pos = + hb_buffer_get_glyph_positions(buffer, NULL); + auto runBuffer = builder->allocRunPos(paint, len); + + 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; + reinterpret_cast(runBuffer.pos)[i] = + SkPoint::Make(x + pos[i].x_offset * textSizeX, + 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/SkShaper.h new file mode 100644 index 0000000000..5aa93a7dbc --- /dev/null +++ b/tools/SkShaper.h @@ -0,0 +1,43 @@ +/* + * 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 + +#include "SkPoint.h" +#include "SkTypeface.h" + +class SkPaint; +class SkTextBlobBuilder; + +/** + Shapes text using harfbuzz and places the shaped text into a + TextBlob. + */ +class SkShaper { +public: + SkShaper(sk_sp face); + ~SkShaper(); + + bool good() const; + SkScalar shape(SkTextBlobBuilder* dest, + const SkPaint& srcPaint, + const char* utf8text, + size_t textBytes, + SkPoint point) const; + +private: + SkShaper(const SkShaper&) = delete; + SkShaper& operator=(const SkShaper&) = delete; + + struct Impl; + std::unique_ptr fImpl; +}; + +#endif // SkShaper_DEFINED diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp index 01a5b02689..782103465e 100644 --- a/tools/using_skia_and_harfbuzz.cpp +++ b/tools/using_skia_and_harfbuzz.cpp @@ -12,327 +12,203 @@ #include #include #include -#include #include - -#include +#include +#include #include "SkCanvas.h" #include "SkDocument.h" +#include "SkShaper.h" #include "SkStream.h" #include "SkTextBlob.h" #include "SkTypeface.h" -struct BaseOption { - std::string selector; - std::string description; - virtual void set(std::string _value) = 0; - virtual std::string valueToString() = 0; +// Options ///////////////////////////////////////////////////////////////////// - BaseOption(std::string _selector, std::string _description) : - selector(_selector), - description(_description) {} - - virtual ~BaseOption() {} -}; +struct BaseOption { + std::string selector; + std::string description; + virtual void set(std::string _value) = 0; + virtual std::string valueToString() = 0; -template struct Option : BaseOption { - T value; - Option(std::string selector, std::string description, T defaultValue) : - BaseOption(selector, description), - value(defaultValue) {} -}; + BaseOption(std::string _selector, std::string _description) + : selector(_selector), description(_description) {} -struct DoubleOption : Option { - 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(selector, description, defaultValue) {} -}; + virtual ~BaseOption() {} -struct SkStringOption : Option { - virtual void set(std::string _value) { - value = _value.c_str(); - } - virtual std::string valueToString() { - return value.c_str(); - } - SkStringOption(std::string selector, std::string description, SkString defaultValue) : - Option(selector, description, defaultValue) {} + static void Init(const std::vector &, int argc, char **argv); }; -struct StdStringOption : Option { - virtual void set(std::string _value) { - value = _value; - } - virtual std::string valueToString() { - return value; - } - StdStringOption(std::string selector, std::string description, std::string defaultValue) : - Option(selector, description, defaultValue) {} +template +struct Option : BaseOption { + T value; + Option(std::string selector, std::string description, T defaultValue) + : BaseOption(selector, description), value(defaultValue) {} }; -struct Config { - DoubleOption *page_width = new DoubleOption("-w", "Page width", 600.0f); - DoubleOption *page_height = new DoubleOption("-h", "Page height", 800.0f); - SkStringOption *title = new SkStringOption("-t", "PDF title", SkString("---")); - SkStringOption *author = new SkStringOption("-a", "PDF author", SkString("---")); - SkStringOption *subject = new SkStringOption("-k", "PDF subject", SkString("---")); - SkStringOption *keywords = new SkStringOption("-c", "PDF keywords", SkString("---")); - SkStringOption *creator = new SkStringOption("-t", "PDF creator", SkString("---")); - StdStringOption *font_file = new StdStringOption("-f", ".ttf font file", ""); - DoubleOption *font_size = new DoubleOption("-z", "Font size", 8.0f); - DoubleOption *left_margin = new DoubleOption("-m", "Left margin", 20.0f); - DoubleOption *line_spacing_ratio = new DoubleOption("-h", "Line spacing ratio", 1.5f); - StdStringOption *output_file_name = new StdStringOption("-o", ".pdf output file name", "out-skiahf.pdf"); - - std::map options = { - { page_width->selector, page_width }, - { page_height->selector, page_height }, - { title->selector, title }, - { author->selector, author }, - { subject->selector, subject }, - { keywords->selector, keywords }, - { creator->selector, creator }, - { font_file->selector, font_file }, - { font_size->selector, font_size }, - { left_margin->selector, left_margin }, - { line_spacing_ratio->selector, line_spacing_ratio }, - { output_file_name->selector, output_file_name }, - }; - - Config(int argc, char **argv) { +void BaseOption::Init(const std::vector &option_list, + int argc, char **argv) { + std::map 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; + 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); } - 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 (auto it = options.begin(); it != options.end(); ++it) { - printf("\t%s\t%s (%s)\n", it->first.c_str(), - it->second->description.c_str(), - it->second->valueToString().c_str()); - } - exit(-1); - } } - } // end of Config::Config +} + +struct DoubleOption : Option { + 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(selector, description, defaultValue) {} }; -const double FONT_SIZE_SCALE = 64.0f; +struct StringOption : Option { + 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(selector, description, defaultValue) {} +}; -struct Face { - struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } }; - std::unique_ptr fHarfBuzzFace; - sk_sp fSkiaTypeface; +// Config ////////////////////////////////////////////////////////////////////// - Face(sk_sp skiaTypeface) : fSkiaTypeface(std::move(skiaTypeface)) { - int index; - std::unique_ptr asset(fSkiaTypeface->openStream(&index)); - size_t size = asset->getLength(); - // TODO(halcanary): avoid this malloc and copy. - char* buffer = (char*)malloc(size); - asset->read(buffer, size); - hb_blob_t* blob = hb_blob_create(buffer, - size, - HB_MEMORY_MODE_READONLY, - nullptr, - free); - assert(blob); - hb_blob_make_immutable(blob); - hb_face_t* face = hb_face_create(blob, (unsigned)index); - hb_blob_destroy(blob); - assert(face); - if (!face) { - fSkiaTypeface.reset(); - return; - } - hb_face_set_index(face, (unsigned)index); - hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm()); - fHarfBuzzFace.reset(face); - } - Face(const char* path, int index) { - // fairly portable mmap impl - auto data = SkData::MakeFromFileName(path); - assert(data); - if (!data) { return; } - fSkiaTypeface = SkTypeface::MakeFromStream(new SkMemoryStream(data), index); - assert(fSkiaTypeface); - if (!fSkiaTypeface) { return; } - auto destroy = [](void *d) { static_cast(d)->unref(); }; - const char* bytes = (const char*)data->data(); - unsigned int size = (unsigned int)data->size(); - hb_blob_t* blob = hb_blob_create(bytes, - size, - HB_MEMORY_MODE_READONLY, - data.release(), - destroy); - assert(blob); - hb_blob_make_immutable(blob); - hb_face_t* face = hb_face_create(blob, (unsigned)index); - hb_blob_destroy(blob); - assert(face); - if (!face) { - fSkiaTypeface.reset(); - return; +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{ + &page_width, &page_height, &title, &author, &subject, + &keywords, &creator, &font_file, &font_size, &left_margin, + &line_spacing_ratio, &output_file_name}, argc, argv); } - hb_face_set_index(face, (unsigned)index); - hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm()); - fHarfBuzzFace.reset(face); - } }; +// Placement /////////////////////////////////////////////////////////////////// + class Placement { - public: - Placement(Config &_config, SkWStream* outputStream) : config(_config) { - const std::string& font_file = config.font_file->value; - if (font_file.size() > 0) { - face = new Face(font_file.c_str(), 0 /* index */); - } else { - face = new Face(SkTypeface::MakeDefault()); +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(config->font_size.value); } - hb_font = hb_font_create(face->fHarfBuzzFace.get()); - - hb_font_set_scale(hb_font, - FONT_SIZE_SCALE * config.font_size->value, - FONT_SIZE_SCALE * config.font_size->value); - hb_ot_font_set_funcs(hb_font); - - SkDocument::PDFMetadata pdf_info; - pdf_info.fTitle = config.title->value; - pdf_info.fAuthor = config.author->value; - pdf_info.fSubject = config.subject->value; - pdf_info.fKeywords = config.keywords->value; - pdf_info.fCreator = config.creator->value; - 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; - pdfDocument = SkDocument::MakePDF(outputStream, SK_ScalarDefaultRasterDPI, - pdf_info, nullptr, true); - assert(pdfDocument); - white_paint.setColor(SK_ColorWHITE); - - glyph_paint.setFlags( - SkPaint::kAntiAlias_Flag | - SkPaint::kSubpixelText_Flag); // ... avoid waggly text when rotating. - glyph_paint.setColor(SK_ColorBLACK); - glyph_paint.setTextSize(config.font_size->value); - glyph_paint.setTypeface(face->fSkiaTypeface); - glyph_paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - - NewPage(); - } // end of Placement - - ~Placement() { - delete face; - hb_font_destroy (hb_font); - } - - void WriteLine(const char *text) { - /* Create hb-buffer and populate. */ - hb_buffer_t *hb_buffer = hb_buffer_create (); - hb_buffer_add_utf8 (hb_buffer, text, -1, 0, -1); - hb_buffer_guess_segment_properties (hb_buffer); - - /* Shape it! */ - hb_shape (hb_font, hb_buffer, NULL, 0); - - DrawGlyphs(hb_buffer); - - hb_buffer_destroy (hb_buffer); + 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(config->page_width.value, + 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, SkPoint{0, 0}); + sk_sp blob(textBlobBuilder.build()); + pageCanvas->drawTextBlob(blob.get(), current_x, current_y, glyph_paint); - // Advance to the next line. - current_y += config.line_spacing_ratio->value * config.font_size->value; - if (current_y > config.page_height->value) { - pdfDocument->endPage(); - NewPage(); + // Advance to the next line. + current_y += config->line_spacing_ratio.value * config->font_size.value; } - } - - bool Close() { - return pdfDocument->close(); - } private: - Config config; - - Face *face; - - hb_font_t *hb_font; - - sk_sp pdfDocument; - - SkCanvas* pageCanvas; - - SkPaint white_paint; - SkPaint glyph_paint; - - double current_x; - double current_y; - - void NewPage() { - pageCanvas = pdfDocument->beginPage(config.page_width->value, 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; - } - - bool DrawGlyphs(hb_buffer_t *hb_buffer) { - SkTextBlobBuilder textBlobBuilder; - unsigned len = hb_buffer_get_length (hb_buffer); - if (len == 0) { - return true; - } - hb_glyph_info_t *info = hb_buffer_get_glyph_infos (hb_buffer, NULL); - hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (hb_buffer, NULL); - auto runBuffer = textBlobBuilder.allocRunPos(glyph_paint, len); + const Config* config; + SkDocument *document; + SkCanvas *pageCanvas; + SkPaint white_paint; + SkPaint glyph_paint; + double current_x; + double current_y; +}; - double x = 0; - double y = 0; - for (unsigned int i = 0; i < len; i++) - { - runBuffer.glyphs[i] = info[i].codepoint; - reinterpret_cast(runBuffer.pos)[i] = SkPoint::Make( - x + pos[i].x_offset / FONT_SIZE_SCALE, - y - pos[i].y_offset / FONT_SIZE_SCALE); - x += pos[i].x_advance / FONT_SIZE_SCALE; - y += pos[i].y_advance / FONT_SIZE_SCALE; - } +//////////////////////////////////////////////////////////////////////////////// - pageCanvas->drawTextBlob(textBlobBuilder.build(), current_x, current_y, glyph_paint); - return true; - } // end of DrawGlyphs -}; // end of Placement class +static sk_sp 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) { +int main(int argc, char **argv) { Config config(argc, argv); + SkFILEWStream wStream(config.output_file_name.value.c_str()); + sk_sp doc = MakePDFDocument(config, &wStream); + assert(doc); + Placement placement(&config, doc.get()); - Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str())); + const std::string &font_file = config.font_file.value; + sk_sp typeface; + if (font_file.size() > 0) { + typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */); + } + SkShaper shaper(typeface); + assert(shaper.good()); for (std::string line; std::getline(std::cin, line);) { - placement.WriteLine(line.c_str()); + placement.WriteLine(shaper, line.c_str(), line.size()); } - placement.Close(); + doc->close(); return 0; } -- 2.34.1