• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "include/core/SkCanvas.h"
20 #include "include/core/SkStream.h"
21 #include "include/core/SkTextBlob.h"
22 #include "include/core/SkTypeface.h"
23 #include "include/docs/SkPDFDocument.h"
24 #include "include/docs/SkPDFJpegHelpers.h"
25 #include "include/ports/SkFontMgr_empty.h"
26 #include "modules/skshaper/include/SkShaper_harfbuzz.h"
27 #include "modules/skshaper/include/SkShaper_skunicode.h"
28 #include "modules/skunicode/include/SkUnicode.h"
29 
30 namespace {
get_unicode()31 sk_sp<SkUnicode> get_unicode() {
32 #if defined(SK_UNICODE_ICU_IMPLEMENTATION)
33     auto unicode = SkUnicodes::ICU::Make();
34     if (unicode) {
35         return unicode;
36     }
37 #endif
38     SkDEBUGFAIL("Only ICU implementation of SkUnicode is supported");
39     return nullptr;
40 }
41 
42 } // namespace
43 
44 // Options /////////////////////////////////////////////////////////////////////
45 
46 struct BaseOption {
47     std::string selector;
48     std::string description;
49     virtual void set(const std::string& _value) = 0;
50     virtual std::string valueToString() = 0;
51 
BaseOptionBaseOption52     BaseOption(std::string _selector, std::string _description)
53             : selector(std::move(_selector))
54             , description(std::move(_description)) {}
55 
~BaseOptionBaseOption56     virtual ~BaseOption() {}
57 
58     static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
59 };
60 
61 template <class T>
62 struct Option : BaseOption {
63     T value;
OptionOption64     Option(std::string _selector, std::string _description, T defaultValue)
65             : BaseOption(std::move(_selector), std::move(_description))
66             , value(defaultValue) {}
67 };
68 
Init(const std::vector<BaseOption * > & option_list,int argc,char ** argv)69 void BaseOption::Init(const std::vector<BaseOption*> &option_list,
70                       int argc, char **argv) {
71     std::map<std::string, BaseOption *> options;
72     for (BaseOption *opt : option_list) {
73         options[opt->selector] = opt;
74     }
75     for (int i = 1; i < argc; i++) {
76         std::string option_selector(argv[i]);
77         auto it = options.find(option_selector);
78         if (it != options.end()) {
79             if (i >= argc) {
80                 break;
81             }
82             const char *option_value = argv[i + 1];
83             it->second->set(option_value);
84             i++;
85         } else {
86             printf("Ignoring unrecognized option: %s.\n", argv[i]);
87             printf("Usage: %s {option value}\n", argv[0]);
88             printf("\tTakes text from stdin and produces pdf file.\n");
89             printf("Supported options:\n");
90             for (BaseOption *opt : option_list) {
91                 printf("\t%s\t%s (%s)\n", opt->selector.c_str(),
92                        opt->description.c_str(), opt->valueToString().c_str());
93             }
94             exit(-1);
95         }
96     }
97 }
98 
99 struct DoubleOption : Option<double> {
setDoubleOption100     void set(const std::string& _value) override { value = atof(_value.c_str()); }
valueToStringDoubleOption101     std::string valueToString() override {
102         std::ostringstream stm;
103         stm << value;
104         return stm.str();
105     }
DoubleOptionDoubleOption106     DoubleOption(std::string _selector, std::string _description, double defaultValue)
107             : Option<double>(std::move(_selector),
108                              std::move(_description),
109                              std::move(defaultValue)) {}
110 };
111 
112 struct StringOption : Option<std::string> {
setStringOption113     void set(const std::string& _value) override { value = _value; }
valueToStringStringOption114     std::string valueToString() override { return value; }
StringOptionStringOption115     StringOption(std::string _selector, std::string _description, std::string defaultValue)
116             : Option<std::string>(std::move(_selector),
117                                   std::move(_description),
118                                   std::move(defaultValue)) {}
119 };
120 
121 // Config //////////////////////////////////////////////////////////////////////
122 
123 struct Config {
124     DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f);
125     DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f);
126     StringOption title = StringOption("-t", "PDF title", "---");
127     StringOption author = StringOption("-a", "PDF author", "---");
128     StringOption subject = StringOption("-k", "PDF subject", "---");
129     StringOption keywords = StringOption("-c", "PDF keywords", "---");
130     StringOption creator = StringOption("-t", "PDF creator", "---");
131     StringOption font_file = StringOption("-f", ".ttf font file", "");
132     DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f);
133     DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f);
134     DoubleOption line_spacing_ratio =
135             DoubleOption("-h", "Line spacing ratio", 0.25f);
136     StringOption output_file_name =
137             StringOption("-o", ".pdf output file name", "out-skiahf.pdf");
138 
ConfigConfig139     Config(int argc, char **argv) {
140         BaseOption::Init(std::vector<BaseOption*>{
141                 &page_width, &page_height, &title, &author, &subject,
142                 &keywords, &creator, &font_file, &font_size, &left_margin,
143                 &line_spacing_ratio, &output_file_name}, argc, argv);
144     }
145 };
146 
147 // Placement ///////////////////////////////////////////////////////////////////
148 
149 class Placement {
150 public:
Placement(const Config * conf,SkDocument * doc)151     Placement(const Config* conf, SkDocument *doc)
152         : config(conf), document(doc), pageCanvas(nullptr) {
153         white_paint.setColor(SK_ColorWHITE);
154         glyph_paint.setColor(SK_ColorBLACK);
155         glyph_paint.setAntiAlias(true);
156         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
157         font.setSubpixel(true);
158         font.setSize(SkDoubleToScalar(config->font_size.value));
159     }
160 
WriteLine(const SkShaper & shaper,const char * text,size_t textBytes)161     void WriteLine(const SkShaper& shaper, const char* text, size_t textBytes) {
162         SkTextBlobBuilderRunHandler textBlobBuilder(text, {0, 0});
163 
164         const SkBidiIterator::Level defaultLevel = SkBidiIterator::kLTR;
165         std::unique_ptr<SkShaper::BiDiRunIterator> bidi =
166                 SkShapers::unicode::BidiRunIterator(get_unicode(), text, textBytes, defaultLevel);
167         SkASSERT(bidi);
168 
169         std::unique_ptr<SkShaper::LanguageRunIterator> language =
170                 SkShaper::MakeStdLanguageRunIterator(text, textBytes);
171         SkASSERT(language);
172 
173         std::unique_ptr<SkShaper::ScriptRunIterator> script =
174                 SkShapers::HB::ScriptRunIterator(text, textBytes);
175         SkASSERT(script);
176 
177         std::unique_ptr<SkShaper::FontRunIterator> fontRuns =
178                 SkShaper::MakeFontMgrRunIterator(text, textBytes, font, SkFontMgr::RefEmpty());
179         SkASSERT(fontRuns);
180 
181         const SkScalar width = config->page_width.value - 2 * config->left_margin.value;
182         shaper.shape(text,
183                      textBytes,
184                      *fontRuns,
185                      *bidi,
186                      *script,
187                      *language,
188                      nullptr,
189                      0,
190                      width,
191                      &textBlobBuilder);
192         SkPoint endPoint = textBlobBuilder.endPoint();
193         sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
194         // If we don't have a page, or if we're not at the start of the page and the blob won't fit
195         if (!pageCanvas ||
196               (current_y > config->line_spacing_ratio.value * config->font_size.value &&
197                current_y + endPoint.y() > config->page_height.value)
198         ) {
199             if (pageCanvas) {
200                 document->endPage();
201             }
202             pageCanvas = document->beginPage(
203                     SkDoubleToScalar(config->page_width.value),
204                     SkDoubleToScalar(config->page_height.value));
205             pageCanvas->drawPaint(white_paint);
206             current_x = config->left_margin.value;
207             current_y = config->line_spacing_ratio.value * config->font_size.value;
208         }
209         pageCanvas->drawTextBlob(
210                 blob.get(), SkDoubleToScalar(current_x),
211                 SkDoubleToScalar(current_y), glyph_paint);
212         // Advance to the next line.
213         current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value;
214     }
215 
216 private:
217     const Config* config;
218     SkDocument *document;
219     SkCanvas *pageCanvas;
220     SkPaint white_paint;
221     SkPaint glyph_paint;
222     SkFont font;
223     double current_x;
224     double current_y;
225 };
226 
227 ////////////////////////////////////////////////////////////////////////////////
228 
MakePDFDocument(const Config & config,SkWStream * wStream)229 static sk_sp<SkDocument> MakePDFDocument(const Config &config, SkWStream *wStream) {
230     SkPDF::Metadata pdf_info;
231     pdf_info.fTitle = config.title.value.c_str();
232     pdf_info.fAuthor = config.author.value.c_str();
233     pdf_info.fSubject = config.subject.value.c_str();
234     pdf_info.fKeywords = config.keywords.value.c_str();
235     pdf_info.fCreator = config.creator.value.c_str();
236     #if 0
237         SkPDF::DateTime now;
238         SkPDFUtils::GetDateTime(&now);
239         pdf_info.fCreation = now;
240         pdf_info.fModified = now;
241         pdf_info.fPDFA = true;
242     #endif
243     pdf_info.jpegDecoder = SkPDF::JPEG::Decode;
244     pdf_info.jpegEncoder = SkPDF::JPEG::Encode;
245     return SkPDF::MakeDocument(wStream, pdf_info);
246 }
247 
main(int argc,char ** argv)248 int main(int argc, char **argv) {
249     Config config(argc, argv);
250     SkFILEWStream wStream(config.output_file_name.value.c_str());
251     sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream);
252     assert(doc);
253     Placement placement(&config, doc.get());
254 
255     const std::string &font_file = config.font_file.value;
256     sk_sp<SkTypeface> typeface;
257     if (font_file.size() > 0) {
258         // There are different font managers for different platforms. See include/ports
259         sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
260         assert(mgr);
261         typeface = mgr->makeFromFile(font_file.c_str(), 0 /* index */);
262     }
263     std::unique_ptr<SkShaper> shaper =
264             SkShapers::HB::ShaperDrivenWrapper(get_unicode(), nullptr);
265     assert(shaper);
266     //SkString line("This is هذا هو الخط a line.");
267     //SkString line("⁧This is a line هذا هو الخط.⁩");
268     for (std::string line; std::getline(std::cin, line);) {
269         placement.WriteLine(*shaper, line.c_str(), line.size());
270     }
271 
272     doc->close();
273     wStream.flush();
274     return 0;
275 }
276