| /* |
| * 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 <iostream> |
| #include <map> |
| #include <string> |
| |
| #include <hb-ot.h> |
| |
| #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 <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 = 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; |
| } |
| 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<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; |
| } |