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
BaseOptionBaseOption34 BaseOption(std::string _selector, std::string _description)
35 : selector(_selector), description(_description) {}
36
~BaseOptionBaseOption37 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;
OptionOption45 Option(std::string selector, std::string description, T defaultValue)
46 : BaseOption(selector, description), value(defaultValue) {}
47 };
48
Init(const std::vector<BaseOption * > & option_list,int argc,char ** argv)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> {
setDoubleOption80 virtual void set(std::string _value) { value = atof(_value.c_str()); }
valueToStringDoubleOption81 virtual std::string valueToString() {
82 std::ostringstream stm;
83 stm << value;
84 return stm.str();
85 }
DoubleOptionDoubleOption86 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> {
setStringOption93 virtual void set(std::string _value) { value = _value; }
valueToStringStringOption94 virtual std::string valueToString() { return value; }
StringOptionStringOption95 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
ConfigConfig119 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:
Placement(const Config * conf,SkDocument * doc)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
WriteLine(const SkShaper & shaper,const char * text,size_t textBytes)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
MakePDFDocument(const Config & config,SkWStream * wStream)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
main(int argc,char ** argv)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