Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright 2016 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 // This sample progam demonstrates how to use Skia and HarfBuzz to
      9 // produce a PDF file from UTF-8 text in stdin.
     10 
     11 #include <cassert>
     12 #include <cstdlib>
     13 #include <iostream>
     14 #include <map>
     15 #include <sstream>
     16 #include <string>
     17 #include <vector>
     18 
     19 #include "SkCanvas.h"
     20 #include "SkDocument.h"
     21 #include "SkShaper.h"
     22 #include "SkStream.h"
     23 #include "SkTextBlob.h"
     24 #include "SkTypeface.h"
     25 
     26 // Options /////////////////////////////////////////////////////////////////////
     27 
     28 struct BaseOption {
     29     std::string selector;
     30     std::string description;
     31     virtual void set(std::string _value) = 0;
     32     virtual std::string valueToString() = 0;
     33 
     34     BaseOption(std::string _selector, std::string _description)
     35         : selector(_selector), description(_description) {}
     36 
     37     virtual ~BaseOption() {}
     38 
     39     static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
     40 };
     41 
     42 template <class T>
     43 struct Option : BaseOption {
     44     T value;
     45     Option(std::string selector, std::string description, T defaultValue)
     46         : BaseOption(selector, description), value(defaultValue) {}
     47 };
     48 
     49 void BaseOption::Init(const std::vector<BaseOption*> &option_list,
     50                       int argc, char **argv) {
     51     std::map<std::string, BaseOption *> options;
     52     for (BaseOption *opt : option_list) {
     53         options[opt->selector] = opt;
     54     }
     55     for (int i = 1; i < argc; i++) {
     56         std::string option_selector(argv[i]);
     57         auto it = options.find(option_selector);
     58         if (it != options.end()) {
     59             if (i >= argc) {
     60                 break;
     61             }
     62             const char *option_value = argv[i + 1];
     63             it->second->set(option_value);
     64             i++;
     65         } else {
     66             printf("Ignoring unrecognized option: %s.\n", argv[i]);
     67             printf("Usage: %s {option value}\n", argv[0]);
     68             printf("\tTakes text from stdin and produces pdf file.\n");
     69             printf("Supported options:\n");
     70             for (BaseOption *opt : option_list) {
     71                 printf("\t%s\t%s (%s)\n", opt->selector.c_str(),
     72                        opt->description.c_str(), opt->valueToString().c_str());
     73             }
     74             exit(-1);
     75         }
     76     }
     77 }
     78 
     79 struct DoubleOption : Option<double> {
     80     virtual void set(std::string _value) { value = atof(_value.c_str()); }
     81     virtual std::string valueToString() {
     82         std::ostringstream stm;
     83         stm << value;
     84         return stm.str();
     85     }
     86     DoubleOption(std::string selector,
     87                  std::string description,
     88                  double defaultValue)
     89         : Option<double>(selector, description, defaultValue) {}
     90 };
     91 
     92 struct StringOption : Option<std::string> {
     93     virtual void set(std::string _value) { value = _value; }
     94     virtual std::string valueToString() { return value; }
     95     StringOption(std::string selector,
     96                  std::string description,
     97                  std::string defaultValue)
     98         : Option<std::string>(selector, description, defaultValue) {}
     99 };
    100 
    101 // Config //////////////////////////////////////////////////////////////////////
    102 
    103 struct Config {
    104     DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f);
    105     DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f);
    106     StringOption title = StringOption("-t", "PDF title", "---");
    107     StringOption author = StringOption("-a", "PDF author", "---");
    108     StringOption subject = StringOption("-k", "PDF subject", "---");
    109     StringOption keywords = StringOption("-c", "PDF keywords", "---");
    110     StringOption creator = StringOption("-t", "PDF creator", "---");
    111     StringOption font_file = StringOption("-f", ".ttf font file", "");
    112     DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f);
    113     DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f);
    114     DoubleOption line_spacing_ratio =
    115             DoubleOption("-h", "Line spacing ratio", 1.5f);
    116     StringOption output_file_name =
    117             StringOption("-o", ".pdf output file name", "out-skiahf.pdf");
    118 
    119     Config(int argc, char **argv) {
    120         BaseOption::Init(std::vector<BaseOption*>{
    121                 &page_width, &page_height, &title, &author, &subject,
    122                 &keywords, &creator, &font_file, &font_size, &left_margin,
    123                 &line_spacing_ratio, &output_file_name}, argc, argv);
    124     }
    125 };
    126 
    127 // Placement ///////////////////////////////////////////////////////////////////
    128 
    129 class Placement {
    130 public:
    131     Placement(const Config* conf, SkDocument *doc)
    132         : config(conf), document(doc), pageCanvas(nullptr) {
    133         white_paint.setColor(SK_ColorWHITE);
    134         glyph_paint.setColor(SK_ColorBLACK);
    135         glyph_paint.setFlags(SkPaint::kAntiAlias_Flag |
    136                              SkPaint::kSubpixelText_Flag);
    137         glyph_paint.setTextSize(SkDoubleToScalar(config->font_size.value));
    138     }
    139 
    140     void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
    141         if (!pageCanvas || current_y > config->page_height.value) {
    142             if (pageCanvas) {
    143                 document->endPage();
    144             }
    145             pageCanvas = document->beginPage(
    146                     SkDoubleToScalar(config->page_width.value),
    147                     SkDoubleToScalar(config->page_height.value));
    148             pageCanvas->drawPaint(white_paint);
    149             current_x = config->left_margin.value;
    150             current_y = config->line_spacing_ratio.value * config->font_size.value;
    151         }
    152         SkTextBlobBuilder textBlobBuilder;
    153         shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, SkPoint{0, 0});
    154         sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
    155         pageCanvas->drawTextBlob(
    156                 blob.get(), SkDoubleToScalar(current_x),
    157                 SkDoubleToScalar(current_y), glyph_paint);
    158         // Advance to the next line.
    159         current_y += config->line_spacing_ratio.value * config->font_size.value;
    160     }
    161 
    162 private:
    163     const Config* config;
    164     SkDocument *document;
    165     SkCanvas *pageCanvas;
    166     SkPaint white_paint;
    167     SkPaint glyph_paint;
    168     double current_x;
    169     double current_y;
    170 };
    171 
    172 ////////////////////////////////////////////////////////////////////////////////
    173 
    174 static sk_sp<SkDocument> MakePDFDocument(const Config &config,
    175                                          SkWStream *wStream) {
    176     SkDocument::PDFMetadata pdf_info;
    177     pdf_info.fTitle = config.title.value.c_str();
    178     pdf_info.fAuthor = config.author.value.c_str();
    179     pdf_info.fSubject = config.subject.value.c_str();
    180     pdf_info.fKeywords = config.keywords.value.c_str();
    181     pdf_info.fCreator = config.creator.value.c_str();
    182     bool pdfa = false;
    183     #if 0
    184         SkTime::DateTime now;
    185         SkTime::GetDateTime(&now);
    186         pdf_info.fCreation.fEnabled = true;
    187         pdf_info.fCreation.fDateTime = now;
    188         pdf_info.fModified.fEnabled = true;
    189         pdf_info.fModified.fDateTime = now;
    190         pdfa = true;
    191     #endif
    192     return SkDocument::MakePDF(wStream, SK_ScalarDefaultRasterDPI, pdf_info,
    193                                nullptr, pdfa);
    194 }
    195 
    196 int main(int argc, char **argv) {
    197     Config config(argc, argv);
    198     SkFILEWStream wStream(config.output_file_name.value.c_str());
    199     sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream);
    200     assert(doc);
    201     Placement placement(&config, doc.get());
    202 
    203     const std::string &font_file = config.font_file.value;
    204     sk_sp<SkTypeface> typeface;
    205     if (font_file.size() > 0) {
    206         typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */);
    207     }
    208     SkShaper shaper(typeface);
    209     assert(shaper.good());
    210     for (std::string line; std::getline(std::cin, line);) {
    211         placement.WriteLine(shaper, line.c_str(), line.size());
    212     }
    213 
    214     doc->close();
    215     return 0;
    216 }
    217