You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
8.0 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* 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 "SkCanvas.h"
#include "SkPDFDocument.h"
#include "SkShaper.h"
#include "SkStream.h"
#include "SkTextBlob.h"
#include "SkTypeface.h"
// Options /////////////////////////////////////////////////////////////////////
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() {}
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(_selector, _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> {
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 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) {}
};
// 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;
SkPoint endPoint = shaper.shape(&textBlobBuilder, font, text, textBytes, true,
SkPoint{0, 0},
config->page_width.value - 2*config->left_margin.value);
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
SkTime::DateTime now;
SkTime::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) {
typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */);
}
SkShaper shaper(typeface);
assert(shaper.good());
//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;
}