blob: 94c85fe9a83ee227e4b7905783452ce8355457f5 [file] [log] [blame]
/*
* 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 <sstream>
#include <string>
#include <vector>
#include "include/core/SkCanvas.h"
#include "include/core/SkStream.h"
#include "include/core/SkTextBlob.h"
#include "include/core/SkTypeface.h"
#include "include/docs/SkPDFDocument.h"
#include "include/ports/SkFontMgr_empty.h"
#include "modules/skshaper/include/SkShaper.h"
// Options /////////////////////////////////////////////////////////////////////
struct BaseOption {
std::string selector;
std::string description;
virtual void set(const std::string& _value) = 0;
virtual std::string valueToString() = 0;
BaseOption(std::string _selector, std::string _description)
: selector(std::move(_selector))
, description(std::move(_description)) {}
virtual ~BaseOption() {}
static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
};
template <class T>
struct Option : BaseOption {
T value;
Option(std::string _selector, std::string _description, T defaultValue)
: BaseOption(std::move(_selector), std::move(_description))
, value(defaultValue) {}
};
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;
}
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);
}
}
}
struct DoubleOption : Option<double> {
void set(const std::string& _value) override { value = atof(_value.c_str()); }
std::string valueToString() override {
std::ostringstream stm;
stm << value;
return stm.str();
}
DoubleOption(std::string _selector, std::string _description, double defaultValue)
: Option<double>(std::move(_selector),
std::move(_description),
std::move(defaultValue)) {}
};
struct StringOption : Option<std::string> {
void set(const std::string& _value) override { value = _value; }
std::string valueToString() override { return value; }
StringOption(std::string _selector, std::string _description, std::string defaultValue)
: Option<std::string>(std::move(_selector),
std::move(_description),
std::move(defaultValue)) {}
};
// Config //////////////////////////////////////////////////////////////////////
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", 0.25f);
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);
}
};
// Placement ///////////////////////////////////////////////////////////////////
class Placement {
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.setAntiAlias(true);
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
font.setSubpixel(true);
font.setSize(SkDoubleToScalar(config->font_size.value));
}
void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
SkTextBlobBuilderRunHandler textBlobBuilder(text, {0, 0});
shaper.shape(text, textBytes, font, true,
config->page_width.value - 2*config->left_margin.value, &textBlobBuilder);
SkPoint endPoint = textBlobBuilder.endPoint();
sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
// If we don't have a page, or if we're not at the start of the page and the blob won't fit
if (!pageCanvas ||
(current_y > config->line_spacing_ratio.value * config->font_size.value &&
current_y + endPoint.y() > config->page_height.value)
) {
if (pageCanvas) {
document->endPage();
}
pageCanvas = document->beginPage(
SkDoubleToScalar(config->page_width.value),
SkDoubleToScalar(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;
}
pageCanvas->drawTextBlob(
blob.get(), SkDoubleToScalar(current_x),
SkDoubleToScalar(current_y), glyph_paint);
// Advance to the next line.
current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value;
}
private:
const Config* config;
SkDocument *document;
SkCanvas *pageCanvas;
SkPaint white_paint;
SkPaint glyph_paint;
SkFont font;
double current_x;
double current_y;
};
////////////////////////////////////////////////////////////////////////////////
static sk_sp<SkDocument> MakePDFDocument(const Config &config, SkWStream *wStream) {
SkPDF::Metadata 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();
#if 0
SkPDF::DateTime now;
SkPDFUtils::GetDateTime(&now);
pdf_info.fCreation = now;
pdf_info.fModified = now;
pdf_info.fPDFA = true;
#endif
return SkPDF::MakeDocument(wStream, pdf_info);
}
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());
const std::string &font_file = config.font_file.value;
sk_sp<SkTypeface> typeface;
if (font_file.size() > 0) {
// There are different font managers for different platforms. See include/ports
sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
assert(mgr);
typeface = mgr->makeFromFile(font_file.c_str(), 0 /* index */);
}
std::unique_ptr<SkShaper> shaper = SkShaper::Make();
assert(shaper);
//SkString line("This is هذا هو الخط a line.");
//SkString line("⁧This is a line هذا هو الخط.⁩");
for (std::string line; std::getline(std::cin, line);) {
placement.WriteLine(*shaper, line.c_str(), line.size());
}
doc->close();
wStream.flush();
return 0;
}