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
28struct 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
42template <class T>
43struct Option : BaseOption {
44    T value;
45    Option(std::string selector, std::string description, T defaultValue)
46        : BaseOption(selector, description), value(defaultValue) {}
47};
48
49void 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
79struct 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
92struct 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
103struct 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", 0.25f);
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
129class Placement {
130public:
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        SkTextBlobBuilder textBlobBuilder;
142        SkPoint endPoint = shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, true,
143                                        SkPoint{0, 0},
144                                        config->page_width.value - 2*config->left_margin.value);
145        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
146        // If we don't have a page, or if we're not at the start of the page and the blob won't fit
147        if (!pageCanvas ||
148              (current_y > config->line_spacing_ratio.value * config->font_size.value &&
149               current_y + endPoint.y() > config->page_height.value)
150        ) {
151            if (pageCanvas) {
152                document->endPage();
153            }
154            pageCanvas = document->beginPage(
155                    SkDoubleToScalar(config->page_width.value),
156                    SkDoubleToScalar(config->page_height.value));
157            pageCanvas->drawPaint(white_paint);
158            current_x = config->left_margin.value;
159            current_y = config->line_spacing_ratio.value * config->font_size.value;
160        }
161        pageCanvas->drawTextBlob(
162                blob.get(), SkDoubleToScalar(current_x),
163                SkDoubleToScalar(current_y), glyph_paint);
164        // Advance to the next line.
165        current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value;
166    }
167
168private:
169    const Config* config;
170    SkDocument *document;
171    SkCanvas *pageCanvas;
172    SkPaint white_paint;
173    SkPaint glyph_paint;
174    double current_x;
175    double current_y;
176};
177
178////////////////////////////////////////////////////////////////////////////////
179
180static sk_sp<SkDocument> MakePDFDocument(const Config &config, SkWStream *wStream) {
181    SkDocument::PDFMetadata pdf_info;
182    pdf_info.fTitle = config.title.value.c_str();
183    pdf_info.fAuthor = config.author.value.c_str();
184    pdf_info.fSubject = config.subject.value.c_str();
185    pdf_info.fKeywords = config.keywords.value.c_str();
186    pdf_info.fCreator = config.creator.value.c_str();
187    #if 0
188        SkTime::DateTime now;
189        SkTime::GetDateTime(&now);
190        pdf_info.fCreation.fEnabled = true;
191        pdf_info.fCreation.fDateTime = now;
192        pdf_info.fModified.fEnabled = true;
193        pdf_info.fModified.fDateTime = now;
194        pdf_info.fPDFA = true;
195    #endif
196    return SkDocument::MakePDF(wStream, pdf_info);
197}
198
199int main(int argc, char **argv) {
200    Config config(argc, argv);
201    SkFILEWStream wStream(config.output_file_name.value.c_str());
202    sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream);
203    assert(doc);
204    Placement placement(&config, doc.get());
205
206    const std::string &font_file = config.font_file.value;
207    sk_sp<SkTypeface> typeface;
208    if (font_file.size() > 0) {
209        typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */);
210    }
211    SkShaper shaper(typeface);
212    assert(shaper.good());
213    //SkString line("This is هذا هو الخط a line.");
214    //SkString line("⁧This is a line هذا هو الخط.⁩");
215    for (std::string line; std::getline(std::cin, line);) {
216        placement.WriteLine(shaper, line.c_str(), line.size());
217    }
218
219    doc->close();
220    wStream.flush();
221    return 0;
222}
223