#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;
}