/* * 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 #include #include #include #include #include #include #include "SkCanvas.h" #include "SkDocument.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; BaseOption(std::string _selector, std::string _description) : selector(_selector), description(_description) {} virtual ~BaseOption() {} }; template struct Option : BaseOption { T value; Option(std::string selector, std::string description, T defaultValue) : BaseOption(selector, description), value(defaultValue) {} }; 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) {} }; 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) {} }; 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) {} }; 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 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 fHarfBuzzFace; sk_sp fSkiaTypeface; 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; } 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); 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 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(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; }