Index: tools/using_skia_and_harfbuzz.cpp |
diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8ed213c46c91a42507190aacf15e54f81e2c4f18 |
--- /dev/null |
+++ b/tools/using_skia_and_harfbuzz.cpp |
@@ -0,0 +1,310 @@ |
+/* |
+ * 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 "SkCanvas.h" |
+#include "SkDocument.h" |
+#include "SkFontMgr.h" |
+#include "SkGradientShader.h" |
+#include "SkPaint.h" |
+#include "SkStream.h" |
+#include "SkString.h" |
+#include "SkTextBlob.h" |
+#include "SkTypeface.h" |
+ |
+#include <hb.h> |
+#include <hb-ot.h> |
+ |
+#include <cassert> |
+#include <iostream> |
+#include <map> |
+#include <string> |
+ |
+ |
+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() {} |
+}; |
+ |
+template <class T> struct Option : BaseOption { |
+ T value; |
+ Option(std::string selector, std::string description, T defaultValue) : |
+ BaseOption(selector, description), |
+ value(defaultValue) {} |
+}; |
+ |
+struct DoubleOption : Option<double> { |
+ virtual void set(std::string _value) { |
+ value = atof(_value.c_str()); |
+ } |
+ virtual std::string valueToString() { |
+ return std::to_string(value); |
+ } |
+ DoubleOption(std::string selector, std::string description, double defaultValue) : |
+ Option<double>(selector, description, defaultValue) {} |
+}; |
+ |
+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) {} |
+}; |
+ |
+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) {} |
+}; |
+ |
+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", "fonts/DejaVuSans.ttf"); |
+ 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) { |
+ 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 (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 |
+}; |
+ |
+const double FONT_SIZE_SCALE = 64.0f; |
+ |
+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; |
+ |
+ Face(const char* path, int index) { |
+ // fairly portable mmap impl |
+ auto data = SkData::MakeFromFileName(path); |
+ assert(data); |
+ if (!data) { return; } |
+ fSkiaTypeface.reset( |
+ SkTypeface::CreateFromStream( |
+ 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; |
+ } |
+ hb_face_set_index(face, (unsigned)index); |
+ hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm()); |
+ fHarfBuzzFace.reset(face); |
+ } |
+}; |
+ |
+class Placement { |
+ public: |
+ Placement(Config &_config, SkWStream* outputStream) : config(_config) { |
+ face = new Face(config.font_file->value.c_str(), 0 /* index */); |
+ 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); |
+ SkAutoTUnref<SkFontMgr> fm(SkFontMgr::RefDefault()); |
+ 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); |
+ |
+ // 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(); |
+ } |
+ } |
+ |
+ 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); |
+ |
+ 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 |
+ |
+int main(int argc, char** argv) { |
+ Config config(argc, argv); |
+ |
+ Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str())); |
+ for (std::string line; std::getline(std::cin, line);) { |
+ placement.WriteLine(line.c_str()); |
+ } |
+ placement.Close(); |
+ |
+ return 0; |
+} |