blob: 01a5b026895e4f5c009b246d9db96c253ffc0537 [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>
halcanarydd05e2a2016-05-12 10:09:58 -070012#include <cstdlib>
halcanary00d44e02016-05-03 15:09:52 -070013#include <iostream>
14#include <map>
15#include <string>
halcanarydd05e2a2016-05-12 10:09:58 -070016#include <sstream>
halcanary00d44e02016-05-03 15:09:52 -070017
halcanary85e38bc2016-05-04 09:34:25 -070018#include <hb-ot.h>
19
20#include "SkCanvas.h"
21#include "SkDocument.h"
22#include "SkStream.h"
23#include "SkTextBlob.h"
24#include "SkTypeface.h"
halcanary00d44e02016-05-03 15:09:52 -070025
26struct BaseOption {
27 std::string selector;
28 std::string description;
29 virtual void set(std::string _value) = 0;
30 virtual std::string valueToString() = 0;
31
32 BaseOption(std::string _selector, std::string _description) :
33 selector(_selector),
34 description(_description) {}
35
36 virtual ~BaseOption() {}
37};
38
39template <class T> struct Option : BaseOption {
40 T value;
41 Option(std::string selector, std::string description, T defaultValue) :
42 BaseOption(selector, description),
43 value(defaultValue) {}
44};
45
46struct DoubleOption : Option<double> {
47 virtual void set(std::string _value) {
48 value = atof(_value.c_str());
49 }
50 virtual std::string valueToString() {
halcanarydd05e2a2016-05-12 10:09:58 -070051 std::ostringstream stm;
52 stm << value;
53 return stm.str();
halcanary00d44e02016-05-03 15:09:52 -070054 }
55 DoubleOption(std::string selector, std::string description, double defaultValue) :
56 Option<double>(selector, description, defaultValue) {}
57};
58
59struct SkStringOption : Option<SkString> {
60 virtual void set(std::string _value) {
61 value = _value.c_str();
62 }
63 virtual std::string valueToString() {
64 return value.c_str();
65 }
66 SkStringOption(std::string selector, std::string description, SkString defaultValue) :
67 Option<SkString>(selector, description, defaultValue) {}
68};
69
70struct StdStringOption : Option<std::string> {
71 virtual void set(std::string _value) {
72 value = _value;
73 }
74 virtual std::string valueToString() {
75 return value;
76 }
77 StdStringOption(std::string selector, std::string description, std::string defaultValue) :
78 Option<std::string>(selector, description, defaultValue) {}
79};
80
81struct Config {
82 DoubleOption *page_width = new DoubleOption("-w", "Page width", 600.0f);
83 DoubleOption *page_height = new DoubleOption("-h", "Page height", 800.0f);
84 SkStringOption *title = new SkStringOption("-t", "PDF title", SkString("---"));
85 SkStringOption *author = new SkStringOption("-a", "PDF author", SkString("---"));
86 SkStringOption *subject = new SkStringOption("-k", "PDF subject", SkString("---"));
87 SkStringOption *keywords = new SkStringOption("-c", "PDF keywords", SkString("---"));
88 SkStringOption *creator = new SkStringOption("-t", "PDF creator", SkString("---"));
halcanary627ad6d2016-07-01 08:48:12 -070089 StdStringOption *font_file = new StdStringOption("-f", ".ttf font file", "");
halcanary00d44e02016-05-03 15:09:52 -070090 DoubleOption *font_size = new DoubleOption("-z", "Font size", 8.0f);
91 DoubleOption *left_margin = new DoubleOption("-m", "Left margin", 20.0f);
92 DoubleOption *line_spacing_ratio = new DoubleOption("-h", "Line spacing ratio", 1.5f);
93 StdStringOption *output_file_name = new StdStringOption("-o", ".pdf output file name", "out-skiahf.pdf");
94
95 std::map<std::string, BaseOption*> options = {
96 { page_width->selector, page_width },
97 { page_height->selector, page_height },
98 { title->selector, title },
99 { author->selector, author },
100 { subject->selector, subject },
101 { keywords->selector, keywords },
102 { creator->selector, creator },
103 { font_file->selector, font_file },
104 { font_size->selector, font_size },
105 { left_margin->selector, left_margin },
106 { line_spacing_ratio->selector, line_spacing_ratio },
107 { output_file_name->selector, output_file_name },
108 };
109
110 Config(int argc, char **argv) {
111 for (int i = 1; i < argc; i++) {
112 std::string option_selector(argv[i]);
113 auto it = options.find(option_selector);
114 if (it != options.end()) {
115 if (i >= argc) {
116 break;
117 }
118 const char *option_value = argv[i + 1];
119 it->second->set(option_value);
120 i++;
121 } else {
122 printf("Ignoring unrecognized option: %s.\n", argv[i]);
123 printf("Usage: %s {option value}\n", argv[0]);
124 printf("\tTakes text from stdin and produces pdf file.\n");
125 printf("Supported options:\n");
126 for (auto it = options.begin(); it != options.end(); ++it) {
127 printf("\t%s\t%s (%s)\n", it->first.c_str(),
128 it->second->description.c_str(),
129 it->second->valueToString().c_str());
130 }
131 exit(-1);
132 }
133 }
134 } // end of Config::Config
135};
136
137const double FONT_SIZE_SCALE = 64.0f;
138
139struct Face {
140 struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } };
141 std::unique_ptr<hb_face_t, HBFDel> fHarfBuzzFace;
142 sk_sp<SkTypeface> fSkiaTypeface;
143
halcanary627ad6d2016-07-01 08:48:12 -0700144 Face(sk_sp<SkTypeface> skiaTypeface) : fSkiaTypeface(std::move(skiaTypeface)) {
145 int index;
146 std::unique_ptr<SkStreamAsset> asset(fSkiaTypeface->openStream(&index));
147 size_t size = asset->getLength();
148 // TODO(halcanary): avoid this malloc and copy.
149 char* buffer = (char*)malloc(size);
150 asset->read(buffer, size);
151 hb_blob_t* blob = hb_blob_create(buffer,
152 size,
153 HB_MEMORY_MODE_READONLY,
154 nullptr,
155 free);
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 }
halcanary00d44e02016-05-03 15:09:52 -0700169 Face(const char* path, int index) {
170 // fairly portable mmap impl
171 auto data = SkData::MakeFromFileName(path);
172 assert(data);
173 if (!data) { return; }
bungeman13b9c952016-05-12 10:09:30 -0700174 fSkiaTypeface = SkTypeface::MakeFromStream(new SkMemoryStream(data), index);
halcanary00d44e02016-05-03 15:09:52 -0700175 assert(fSkiaTypeface);
176 if (!fSkiaTypeface) { return; }
177 auto destroy = [](void *d) { static_cast<SkData*>(d)->unref(); };
178 const char* bytes = (const char*)data->data();
179 unsigned int size = (unsigned int)data->size();
180 hb_blob_t* blob = hb_blob_create(bytes,
181 size,
182 HB_MEMORY_MODE_READONLY,
183 data.release(),
184 destroy);
185 assert(blob);
186 hb_blob_make_immutable(blob);
187 hb_face_t* face = hb_face_create(blob, (unsigned)index);
188 hb_blob_destroy(blob);
189 assert(face);
190 if (!face) {
191 fSkiaTypeface.reset();
192 return;
193 }
194 hb_face_set_index(face, (unsigned)index);
195 hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm());
196 fHarfBuzzFace.reset(face);
197 }
198};
199
200class Placement {
201 public:
202 Placement(Config &_config, SkWStream* outputStream) : config(_config) {
halcanary627ad6d2016-07-01 08:48:12 -0700203 const std::string& font_file = config.font_file->value;
204 if (font_file.size() > 0) {
205 face = new Face(font_file.c_str(), 0 /* index */);
206 } else {
207 face = new Face(SkTypeface::MakeDefault());
208 }
halcanary00d44e02016-05-03 15:09:52 -0700209 hb_font = hb_font_create(face->fHarfBuzzFace.get());
210
211 hb_font_set_scale(hb_font,
212 FONT_SIZE_SCALE * config.font_size->value,
213 FONT_SIZE_SCALE * config.font_size->value);
214 hb_ot_font_set_funcs(hb_font);
215
216 SkDocument::PDFMetadata pdf_info;
217 pdf_info.fTitle = config.title->value;
218 pdf_info.fAuthor = config.author->value;
219 pdf_info.fSubject = config.subject->value;
220 pdf_info.fKeywords = config.keywords->value;
221 pdf_info.fCreator = config.creator->value;
222 SkTime::DateTime now;
223 SkTime::GetDateTime(&now);
224 pdf_info.fCreation.fEnabled = true;
225 pdf_info.fCreation.fDateTime = now;
226 pdf_info.fModified.fEnabled = true;
227 pdf_info.fModified.fDateTime = now;
228 pdfDocument = SkDocument::MakePDF(outputStream, SK_ScalarDefaultRasterDPI,
229 pdf_info, nullptr, true);
230 assert(pdfDocument);
231
232 white_paint.setColor(SK_ColorWHITE);
233
234 glyph_paint.setFlags(
235 SkPaint::kAntiAlias_Flag |
236 SkPaint::kSubpixelText_Flag); // ... avoid waggly text when rotating.
237 glyph_paint.setColor(SK_ColorBLACK);
238 glyph_paint.setTextSize(config.font_size->value);
halcanary00d44e02016-05-03 15:09:52 -0700239 glyph_paint.setTypeface(face->fSkiaTypeface);
240 glyph_paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
241
242 NewPage();
243 } // end of Placement
244
245 ~Placement() {
246 delete face;
247 hb_font_destroy (hb_font);
248 }
249
250 void WriteLine(const char *text) {
251 /* Create hb-buffer and populate. */
252 hb_buffer_t *hb_buffer = hb_buffer_create ();
253 hb_buffer_add_utf8 (hb_buffer, text, -1, 0, -1);
254 hb_buffer_guess_segment_properties (hb_buffer);
255
256 /* Shape it! */
257 hb_shape (hb_font, hb_buffer, NULL, 0);
258
259 DrawGlyphs(hb_buffer);
260
261 hb_buffer_destroy (hb_buffer);
262
263 // Advance to the next line.
264 current_y += config.line_spacing_ratio->value * config.font_size->value;
265 if (current_y > config.page_height->value) {
266 pdfDocument->endPage();
267 NewPage();
268 }
269 }
270
271 bool Close() {
272 return pdfDocument->close();
273 }
274
275private:
276 Config config;
277
278 Face *face;
279
280 hb_font_t *hb_font;
281
282 sk_sp<SkDocument> pdfDocument;
283
284 SkCanvas* pageCanvas;
285
286 SkPaint white_paint;
287 SkPaint glyph_paint;
288
289 double current_x;
290 double current_y;
291
292 void NewPage() {
293 pageCanvas = pdfDocument->beginPage(config.page_width->value, config.page_height->value);
294
295 pageCanvas->drawPaint(white_paint);
296
297 current_x = config.left_margin->value;
298 current_y = config.line_spacing_ratio->value * config.font_size->value;
299 }
300
301 bool DrawGlyphs(hb_buffer_t *hb_buffer) {
302 SkTextBlobBuilder textBlobBuilder;
303 unsigned len = hb_buffer_get_length (hb_buffer);
304 if (len == 0) {
305 return true;
306 }
307 hb_glyph_info_t *info = hb_buffer_get_glyph_infos (hb_buffer, NULL);
308 hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (hb_buffer, NULL);
309 auto runBuffer = textBlobBuilder.allocRunPos(glyph_paint, len);
310
311 double x = 0;
312 double y = 0;
313 for (unsigned int i = 0; i < len; i++)
314 {
315 runBuffer.glyphs[i] = info[i].codepoint;
316 reinterpret_cast<SkPoint*>(runBuffer.pos)[i] = SkPoint::Make(
317 x + pos[i].x_offset / FONT_SIZE_SCALE,
318 y - pos[i].y_offset / FONT_SIZE_SCALE);
319 x += pos[i].x_advance / FONT_SIZE_SCALE;
320 y += pos[i].y_advance / FONT_SIZE_SCALE;
321 }
322
323 pageCanvas->drawTextBlob(textBlobBuilder.build(), current_x, current_y, glyph_paint);
324 return true;
325 } // end of DrawGlyphs
326}; // end of Placement class
327
328int main(int argc, char** argv) {
329 Config config(argc, argv);
330
331 Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str()));
332 for (std::string line; std::getline(std::cin, line);) {
333 placement.WriteLine(line.c_str());
334 }
335 placement.Close();
336
337 return 0;
338}