skia2/tools/using_skia_and_harfbuzz.cpp

339 lines
11 KiB
C++
Raw Normal View History

/*
* 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 <cstdlib>
#include <iostream>
#include <map>
#include <string>
#include <sstream>
#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() {
std::ostringstream stm;
stm << value;
return stm.str();
}
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", "");
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(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;
}
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) {
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());
}
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;
}