blob: 86c9accd33212ae65229da4f1b10297dad54b4ad [file] [log] [blame]
halcanary00d44e02016-05-03 15:09:52 -07001/*
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
halcanary85e38bc2016-05-04 09:34:25 -07008// This sample progam demonstrates how to use Skia and HarfBuzz to
9// produce a PDF file from UTF-8 text in stdin.
halcanary00d44e02016-05-03 15:09:52 -070010
11#include <cassert>
12#include <iostream>
13#include <map>
14#include <string>
15
halcanary85e38bc2016-05-04 09:34:25 -070016#include <hb-ot.h>
17
18#include "SkCanvas.h"
19#include "SkDocument.h"
20#include "SkStream.h"
21#include "SkTextBlob.h"
22#include "SkTypeface.h"
halcanary00d44e02016-05-03 15:09:52 -070023
24struct BaseOption {
25 std::string selector;
26 std::string description;
27 virtual void set(std::string _value) = 0;
28 virtual std::string valueToString() = 0;
29
30 BaseOption(std::string _selector, std::string _description) :
31 selector(_selector),
32 description(_description) {}
33
34 virtual ~BaseOption() {}
35};
36
37template <class T> struct Option : BaseOption {
38 T value;
39 Option(std::string selector, std::string description, T defaultValue) :
40 BaseOption(selector, description),
41 value(defaultValue) {}
42};
43
44struct DoubleOption : Option<double> {
45 virtual void set(std::string _value) {
46 value = atof(_value.c_str());
47 }
48 virtual std::string valueToString() {
49 return std::to_string(value);
50 }
51 DoubleOption(std::string selector, std::string description, double defaultValue) :
52 Option<double>(selector, description, defaultValue) {}
53};
54
55struct SkStringOption : Option<SkString> {
56 virtual void set(std::string _value) {
57 value = _value.c_str();
58 }
59 virtual std::string valueToString() {
60 return value.c_str();
61 }
62 SkStringOption(std::string selector, std::string description, SkString defaultValue) :
63 Option<SkString>(selector, description, defaultValue) {}
64};
65
66struct StdStringOption : Option<std::string> {
67 virtual void set(std::string _value) {
68 value = _value;
69 }
70 virtual std::string valueToString() {
71 return value;
72 }
73 StdStringOption(std::string selector, std::string description, std::string defaultValue) :
74 Option<std::string>(selector, description, defaultValue) {}
75};
76
77struct Config {
78 DoubleOption *page_width = new DoubleOption("-w", "Page width", 600.0f);
79 DoubleOption *page_height = new DoubleOption("-h", "Page height", 800.0f);
80 SkStringOption *title = new SkStringOption("-t", "PDF title", SkString("---"));
81 SkStringOption *author = new SkStringOption("-a", "PDF author", SkString("---"));
82 SkStringOption *subject = new SkStringOption("-k", "PDF subject", SkString("---"));
83 SkStringOption *keywords = new SkStringOption("-c", "PDF keywords", SkString("---"));
84 SkStringOption *creator = new SkStringOption("-t", "PDF creator", SkString("---"));
85 StdStringOption *font_file = new StdStringOption("-f", ".ttf font file", "fonts/DejaVuSans.ttf");
86 DoubleOption *font_size = new DoubleOption("-z", "Font size", 8.0f);
87 DoubleOption *left_margin = new DoubleOption("-m", "Left margin", 20.0f);
88 DoubleOption *line_spacing_ratio = new DoubleOption("-h", "Line spacing ratio", 1.5f);
89 StdStringOption *output_file_name = new StdStringOption("-o", ".pdf output file name", "out-skiahf.pdf");
90
91 std::map<std::string, BaseOption*> options = {
92 { page_width->selector, page_width },
93 { page_height->selector, page_height },
94 { title->selector, title },
95 { author->selector, author },
96 { subject->selector, subject },
97 { keywords->selector, keywords },
98 { creator->selector, creator },
99 { font_file->selector, font_file },
100 { font_size->selector, font_size },
101 { left_margin->selector, left_margin },
102 { line_spacing_ratio->selector, line_spacing_ratio },
103 { output_file_name->selector, output_file_name },
104 };
105
106 Config(int argc, char **argv) {
107 for (int i = 1; i < argc; i++) {
108 std::string option_selector(argv[i]);
109 auto it = options.find(option_selector);
110 if (it != options.end()) {
111 if (i >= argc) {
112 break;
113 }
114 const char *option_value = argv[i + 1];
115 it->second->set(option_value);
116 i++;
117 } else {
118 printf("Ignoring unrecognized option: %s.\n", argv[i]);
119 printf("Usage: %s {option value}\n", argv[0]);
120 printf("\tTakes text from stdin and produces pdf file.\n");
121 printf("Supported options:\n");
122 for (auto it = options.begin(); it != options.end(); ++it) {
123 printf("\t%s\t%s (%s)\n", it->first.c_str(),
124 it->second->description.c_str(),
125 it->second->valueToString().c_str());
126 }
127 exit(-1);
128 }
129 }
130 } // end of Config::Config
131};
132
133const double FONT_SIZE_SCALE = 64.0f;
134
135struct Face {
136 struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } };
137 std::unique_ptr<hb_face_t, HBFDel> fHarfBuzzFace;
138 sk_sp<SkTypeface> fSkiaTypeface;
139
140 Face(const char* path, int index) {
141 // fairly portable mmap impl
142 auto data = SkData::MakeFromFileName(path);
143 assert(data);
144 if (!data) { return; }
bungeman6296da72016-05-11 12:38:18 -0700145 fSkiaTypeface = SkTypeface::MakeFromStream(new SkMemoryStream(data), index);
halcanary00d44e02016-05-03 15:09:52 -0700146 assert(fSkiaTypeface);
147 if (!fSkiaTypeface) { return; }
148 auto destroy = [](void *d) { static_cast<SkData*>(d)->unref(); };
149 const char* bytes = (const char*)data->data();
150 unsigned int size = (unsigned int)data->size();
151 hb_blob_t* blob = hb_blob_create(bytes,
152 size,
153 HB_MEMORY_MODE_READONLY,
154 data.release(),
155 destroy);
156 assert(blob);
157 hb_blob_make_immutable(blob);
158 hb_face_t* face = hb_face_create(blob, (unsigned)index);
159 hb_blob_destroy(blob);
160 assert(face);
161 if (!face) {
162 fSkiaTypeface.reset();
163 return;
164 }
165 hb_face_set_index(face, (unsigned)index);
166 hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm());
167 fHarfBuzzFace.reset(face);
168 }
169};
170
171class Placement {
172 public:
173 Placement(Config &_config, SkWStream* outputStream) : config(_config) {
174 face = new Face(config.font_file->value.c_str(), 0 /* index */);
175 hb_font = hb_font_create(face->fHarfBuzzFace.get());
176
177 hb_font_set_scale(hb_font,
178 FONT_SIZE_SCALE * config.font_size->value,
179 FONT_SIZE_SCALE * config.font_size->value);
180 hb_ot_font_set_funcs(hb_font);
181
182 SkDocument::PDFMetadata pdf_info;
183 pdf_info.fTitle = config.title->value;
184 pdf_info.fAuthor = config.author->value;
185 pdf_info.fSubject = config.subject->value;
186 pdf_info.fKeywords = config.keywords->value;
187 pdf_info.fCreator = config.creator->value;
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 pdfDocument = SkDocument::MakePDF(outputStream, SK_ScalarDefaultRasterDPI,
195 pdf_info, nullptr, true);
196 assert(pdfDocument);
197
198 white_paint.setColor(SK_ColorWHITE);
199
200 glyph_paint.setFlags(
201 SkPaint::kAntiAlias_Flag |
202 SkPaint::kSubpixelText_Flag); // ... avoid waggly text when rotating.
203 glyph_paint.setColor(SK_ColorBLACK);
204 glyph_paint.setTextSize(config.font_size->value);
halcanary00d44e02016-05-03 15:09:52 -0700205 glyph_paint.setTypeface(face->fSkiaTypeface);
206 glyph_paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
207
208 NewPage();
209 } // end of Placement
210
211 ~Placement() {
212 delete face;
213 hb_font_destroy (hb_font);
214 }
215
216 void WriteLine(const char *text) {
217 /* Create hb-buffer and populate. */
218 hb_buffer_t *hb_buffer = hb_buffer_create ();
219 hb_buffer_add_utf8 (hb_buffer, text, -1, 0, -1);
220 hb_buffer_guess_segment_properties (hb_buffer);
221
222 /* Shape it! */
223 hb_shape (hb_font, hb_buffer, NULL, 0);
224
225 DrawGlyphs(hb_buffer);
226
227 hb_buffer_destroy (hb_buffer);
228
229 // Advance to the next line.
230 current_y += config.line_spacing_ratio->value * config.font_size->value;
231 if (current_y > config.page_height->value) {
232 pdfDocument->endPage();
233 NewPage();
234 }
235 }
236
237 bool Close() {
238 return pdfDocument->close();
239 }
240
241private:
242 Config config;
243
244 Face *face;
245
246 hb_font_t *hb_font;
247
248 sk_sp<SkDocument> pdfDocument;
249
250 SkCanvas* pageCanvas;
251
252 SkPaint white_paint;
253 SkPaint glyph_paint;
254
255 double current_x;
256 double current_y;
257
258 void NewPage() {
259 pageCanvas = pdfDocument->beginPage(config.page_width->value, config.page_height->value);
260
261 pageCanvas->drawPaint(white_paint);
262
263 current_x = config.left_margin->value;
264 current_y = config.line_spacing_ratio->value * config.font_size->value;
265 }
266
267 bool DrawGlyphs(hb_buffer_t *hb_buffer) {
268 SkTextBlobBuilder textBlobBuilder;
269 unsigned len = hb_buffer_get_length (hb_buffer);
270 if (len == 0) {
271 return true;
272 }
273 hb_glyph_info_t *info = hb_buffer_get_glyph_infos (hb_buffer, NULL);
274 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (hb_buffer, NULL);
275 auto runBuffer = textBlobBuilder.allocRunPos(glyph_paint, len);
276
277 double x = 0;
278 double y = 0;
279 for (unsigned int i = 0; i < len; i++)
280 {
281 runBuffer.glyphs[i] = info[i].codepoint;
282 reinterpret_cast<SkPoint*>(runBuffer.pos)[i] = SkPoint::Make(
283 x + pos[i].x_offset / FONT_SIZE_SCALE,
284 y - pos[i].y_offset / FONT_SIZE_SCALE);
285 x += pos[i].x_advance / FONT_SIZE_SCALE;
286 y += pos[i].y_advance / FONT_SIZE_SCALE;
287 }
288
289 pageCanvas->drawTextBlob(textBlobBuilder.build(), current_x, current_y, glyph_paint);
290 return true;
291 } // end of DrawGlyphs
292}; // end of Placement class
293
294int main(int argc, char** argv) {
295 Config config(argc, argv);
296
297 Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str()));
298 for (std::string line; std::getline(std::cin, line);) {
299 placement.WriteLine(line.c_str());
300 }
301 placement.Close();
302
303 return 0;
304}