| Index: tools/using_skia_and_harfbuzz.cpp
|
| diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp
|
| index 01a5b026895e4f5c009b246d9db96c253ffc0537..782103465eaea72916a629e7a6ddff80c3a04f9b 100644
|
| --- a/tools/using_skia_and_harfbuzz.cpp
|
| +++ b/tools/using_skia_and_harfbuzz.cpp
|
| @@ -12,327 +12,203 @@
|
| #include <cstdlib>
|
| #include <iostream>
|
| #include <map>
|
| -#include <string>
|
| #include <sstream>
|
| -
|
| -#include <hb-ot.h>
|
| +#include <string>
|
| +#include <vector>
|
|
|
| #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 <class T> 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<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) {}
|
| -};
|
| + virtual ~BaseOption() {}
|
|
|
| -struct SkStringOption : Option<SkString> {
|
| - 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<SkString>(selector, description, defaultValue) {}
|
| + static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
|
| };
|
|
|
| -struct StdStringOption : Option<std::string> {
|
| - 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<std::string>(selector, description, defaultValue) {}
|
| +template <class T>
|
| +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<std::string, BaseOption*> 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<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;
|
| + 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<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) {}
|
| };
|
|
|
| -const double FONT_SIZE_SCALE = 64.0f;
|
| +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) {}
|
| +};
|
|
|
| -struct Face {
|
| - struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } };
|
| - std::unique_ptr<hb_face_t, HBFDel> fHarfBuzzFace;
|
| - sk_sp<SkTypeface> fSkiaTypeface;
|
| +// Config //////////////////////////////////////////////////////////////////////
|
|
|
| - Face(sk_sp<SkTypeface> skiaTypeface) : fSkiaTypeface(std::move(skiaTypeface)) {
|
| - int index;
|
| - std::unique_ptr<SkStreamAsset> 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<SkData*>(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<BaseOption*>{
|
| + &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<const SkTextBlob> 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<SkDocument> 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<SkPoint*>(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<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) {
|
| +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());
|
|
|
| - Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str()));
|
| + 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());
|
| 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;
|
| }
|
|
|