| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 // This sample progam demonstrates how to use Skia and HarfBuzz to | 8 // This sample progam demonstrates how to use Skia and HarfBuzz to |
| 9 // produce a PDF file from UTF-8 text in stdin. | 9 // produce a PDF file from UTF-8 text in stdin. |
| 10 | 10 |
| 11 #include <cassert> | 11 #include <cassert> |
| 12 #include <cstdlib> | 12 #include <cstdlib> |
| 13 #include <iostream> | 13 #include <iostream> |
| 14 #include <map> | 14 #include <map> |
| 15 #include <sstream> |
| 15 #include <string> | 16 #include <string> |
| 16 #include <sstream> | 17 #include <vector> |
| 17 | |
| 18 #include <hb-ot.h> | |
| 19 | 18 |
| 20 #include "SkCanvas.h" | 19 #include "SkCanvas.h" |
| 21 #include "SkDocument.h" | 20 #include "SkDocument.h" |
| 21 #include "SkShaper.h" |
| 22 #include "SkStream.h" | 22 #include "SkStream.h" |
| 23 #include "SkTextBlob.h" | 23 #include "SkTextBlob.h" |
| 24 #include "SkTypeface.h" | 24 #include "SkTypeface.h" |
| 25 | 25 |
| 26 // Options ///////////////////////////////////////////////////////////////////// |
| 27 |
| 26 struct BaseOption { | 28 struct BaseOption { |
| 27 std::string selector; | 29 std::string selector; |
| 28 std::string description; | 30 std::string description; |
| 29 virtual void set(std::string _value) = 0; | 31 virtual void set(std::string _value) = 0; |
| 30 virtual std::string valueToString() = 0; | 32 virtual std::string valueToString() = 0; |
| 31 | 33 |
| 32 BaseOption(std::string _selector, std::string _description) : | 34 BaseOption(std::string _selector, std::string _description) |
| 33 selector(_selector), | 35 : selector(_selector), description(_description) {} |
| 34 description(_description) {} | 36 |
| 35 | 37 virtual ~BaseOption() {} |
| 36 virtual ~BaseOption() {} | 38 |
| 37 }; | 39 static void Init(const std::vector<BaseOption*> &, int argc, char **argv); |
| 38 | 40 }; |
| 39 template <class T> struct Option : BaseOption { | 41 |
| 40 T value; | 42 template <class T> |
| 41 Option(std::string selector, std::string description, T defaultValue) : | 43 struct Option : BaseOption { |
| 42 BaseOption(selector, description), | 44 T value; |
| 43 value(defaultValue) {} | 45 Option(std::string selector, std::string description, T defaultValue) |
| 44 }; | 46 : BaseOption(selector, description), value(defaultValue) {} |
| 47 }; |
| 48 |
| 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 } |
| 45 | 78 |
| 46 struct DoubleOption : Option<double> { | 79 struct DoubleOption : Option<double> { |
| 47 virtual void set(std::string _value) { | 80 virtual void set(std::string _value) { value = atof(_value.c_str()); } |
| 48 value = atof(_value.c_str()); | 81 virtual std::string valueToString() { |
| 49 } | 82 std::ostringstream stm; |
| 50 virtual std::string valueToString() { | 83 stm << value; |
| 51 std::ostringstream stm; | 84 return stm.str(); |
| 52 stm << value; | 85 } |
| 53 return stm.str(); | 86 DoubleOption(std::string selector, |
| 54 } | 87 std::string description, |
| 55 DoubleOption(std::string selector, std::string description, double defaultValu
e) : | 88 double defaultValue) |
| 56 Option<double>(selector, description, defaultValue) {} | 89 : Option<double>(selector, description, defaultValue) {} |
| 57 }; | 90 }; |
| 58 | 91 |
| 59 struct SkStringOption : Option<SkString> { | 92 struct StringOption : Option<std::string> { |
| 60 virtual void set(std::string _value) { | 93 virtual void set(std::string _value) { value = _value; } |
| 61 value = _value.c_str(); | 94 virtual std::string valueToString() { return value; } |
| 62 } | 95 StringOption(std::string selector, |
| 63 virtual std::string valueToString() { | 96 std::string description, |
| 64 return value.c_str(); | 97 std::string defaultValue) |
| 65 } | 98 : Option<std::string>(selector, description, defaultValue) {} |
| 66 SkStringOption(std::string selector, std::string description, SkString default
Value) : | 99 }; |
| 67 Option<SkString>(selector, description, defaultValue) {} | 100 |
| 68 }; | 101 // Config ////////////////////////////////////////////////////////////////////// |
| 69 | |
| 70 struct 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 def
aultValue) : | |
| 78 Option<std::string>(selector, description, defaultValue) {} | |
| 79 }; | |
| 80 | 102 |
| 81 struct Config { | 103 struct Config { |
| 82 DoubleOption *page_width = new DoubleOption("-w", "Page width", 600.0f); | 104 DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f); |
| 83 DoubleOption *page_height = new DoubleOption("-h", "Page height", 800.0f); | 105 DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f); |
| 84 SkStringOption *title = new SkStringOption("-t", "PDF title", SkString("---"))
; | 106 StringOption title = StringOption("-t", "PDF title", "---"); |
| 85 SkStringOption *author = new SkStringOption("-a", "PDF author", SkString("---"
)); | 107 StringOption author = StringOption("-a", "PDF author", "---"); |
| 86 SkStringOption *subject = new SkStringOption("-k", "PDF subject", SkString("--
-")); | 108 StringOption subject = StringOption("-k", "PDF subject", "---"); |
| 87 SkStringOption *keywords = new SkStringOption("-c", "PDF keywords", SkString("
---")); | 109 StringOption keywords = StringOption("-c", "PDF keywords", "---"); |
| 88 SkStringOption *creator = new SkStringOption("-t", "PDF creator", SkString("--
-")); | 110 StringOption creator = StringOption("-t", "PDF creator", "---"); |
| 89 StdStringOption *font_file = new StdStringOption("-f", ".ttf font file", ""); | 111 StringOption font_file = StringOption("-f", ".ttf font file", ""); |
| 90 DoubleOption *font_size = new DoubleOption("-z", "Font size", 8.0f); | 112 DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f); |
| 91 DoubleOption *left_margin = new DoubleOption("-m", "Left margin", 20.0f); | 113 DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f); |
| 92 DoubleOption *line_spacing_ratio = new DoubleOption("-h", "Line spacing ratio"
, 1.5f); | 114 DoubleOption line_spacing_ratio = |
| 93 StdStringOption *output_file_name = new StdStringOption("-o", ".pdf output fil
e name", "out-skiahf.pdf"); | 115 DoubleOption("-h", "Line spacing ratio", 1.5f); |
| 94 | 116 StringOption output_file_name = |
| 95 std::map<std::string, BaseOption*> options = { | 117 StringOption("-o", ".pdf output file name", "out-skiahf.pdf"); |
| 96 { page_width->selector, page_width }, | 118 |
| 97 { page_height->selector, page_height }, | 119 Config(int argc, char **argv) { |
| 98 { title->selector, title }, | 120 BaseOption::Init(std::vector<BaseOption*>{ |
| 99 { author->selector, author }, | 121 &page_width, &page_height, &title, &author, &subject, |
| 100 { subject->selector, subject }, | 122 &keywords, &creator, &font_file, &font_size, &left_margin, |
| 101 { keywords->selector, keywords }, | 123 &line_spacing_ratio, &output_file_name}, argc, argv); |
| 102 { creator->selector, creator }, | 124 } |
| 103 { font_file->selector, font_file }, | 125 }; |
| 104 { font_size->selector, font_size }, | 126 |
| 105 { left_margin->selector, left_margin }, | 127 // Placement /////////////////////////////////////////////////////////////////// |
| 106 { line_spacing_ratio->selector, line_spacing_ratio }, | 128 |
| 107 { output_file_name->selector, output_file_name }, | 129 class Placement { |
| 108 }; | 130 public: |
| 109 | 131 Placement(const Config* conf, SkDocument *doc) |
| 110 Config(int argc, char **argv) { | 132 : config(conf), document(doc), pageCanvas(nullptr) { |
| 111 for (int i = 1; i < argc; i++) { | 133 white_paint.setColor(SK_ColorWHITE); |
| 112 std::string option_selector(argv[i]); | 134 glyph_paint.setColor(SK_ColorBLACK); |
| 113 auto it = options.find(option_selector); | 135 glyph_paint.setFlags(SkPaint::kAntiAlias_Flag | |
| 114 if (it != options.end()) { | 136 SkPaint::kSubpixelText_Flag); |
| 115 if (i >= argc) { | 137 glyph_paint.setTextSize(config->font_size.value); |
| 116 break; | 138 } |
| 139 |
| 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(config->page_width.value, |
| 146 config->page_height.value); |
| 147 pageCanvas->drawPaint(white_paint); |
| 148 current_x = config->left_margin.value; |
| 149 current_y = config->line_spacing_ratio.value * config->font_size.val
ue; |
| 117 } | 150 } |
| 118 const char *option_value = argv[i + 1]; | 151 SkTextBlobBuilder textBlobBuilder; |
| 119 it->second->set(option_value); | 152 shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, SkPoint{0,
0}); |
| 120 i++; | 153 sk_sp<const SkTextBlob> blob(textBlobBuilder.build()); |
| 121 } else { | 154 pageCanvas->drawTextBlob(blob.get(), current_x, current_y, glyph_paint); |
| 122 printf("Ignoring unrecognized option: %s.\n", argv[i]); | 155 |
| 123 printf("Usage: %s {option value}\n", argv[0]); | 156 // Advance to the next line. |
| 124 printf("\tTakes text from stdin and produces pdf file.\n"); | 157 current_y += config->line_spacing_ratio.value * config->font_size.value; |
| 125 printf("Supported options:\n"); | 158 } |
| 126 for (auto it = options.begin(); it != options.end(); ++it) { | 159 |
| 127 printf("\t%s\t%s (%s)\n", it->first.c_str(), | 160 private: |
| 128 it->second->description.c_str(), | 161 const Config* config; |
| 129 it->second->valueToString().c_str()); | 162 SkDocument *document; |
| 130 } | 163 SkCanvas *pageCanvas; |
| 131 exit(-1); | 164 SkPaint white_paint; |
| 132 } | 165 SkPaint glyph_paint; |
| 133 } | 166 double current_x; |
| 134 } // end of Config::Config | 167 double current_y; |
| 135 }; | 168 }; |
| 136 | 169 |
| 137 const double FONT_SIZE_SCALE = 64.0f; | 170 //////////////////////////////////////////////////////////////////////////////// |
| 138 | 171 |
| 139 struct Face { | 172 static sk_sp<SkDocument> MakePDFDocument(const Config &config, |
| 140 struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } }; | 173 SkWStream *wStream) { |
| 141 std::unique_ptr<hb_face_t, HBFDel> fHarfBuzzFace; | 174 SkDocument::PDFMetadata pdf_info; |
| 142 sk_sp<SkTypeface> fSkiaTypeface; | 175 pdf_info.fTitle = config.title.value.c_str(); |
| 143 | 176 pdf_info.fAuthor = config.author.value.c_str(); |
| 144 Face(sk_sp<SkTypeface> skiaTypeface) : fSkiaTypeface(std::move(skiaTypeface))
{ | 177 pdf_info.fSubject = config.subject.value.c_str(); |
| 145 int index; | 178 pdf_info.fKeywords = config.keywords.value.c_str(); |
| 146 std::unique_ptr<SkStreamAsset> asset(fSkiaTypeface->openStream(&index)); | 179 pdf_info.fCreator = config.creator.value.c_str(); |
| 147 size_t size = asset->getLength(); | 180 bool pdfa = false; |
| 148 // TODO(halcanary): avoid this malloc and copy. | 181 #if 0 |
| 149 char* buffer = (char*)malloc(size); | 182 SkTime::DateTime now; |
| 150 asset->read(buffer, size); | 183 SkTime::GetDateTime(&now); |
| 151 hb_blob_t* blob = hb_blob_create(buffer, | 184 pdf_info.fCreation.fEnabled = true; |
| 152 size, | 185 pdf_info.fCreation.fDateTime = now; |
| 153 HB_MEMORY_MODE_READONLY, | 186 pdf_info.fModified.fEnabled = true; |
| 154 nullptr, | 187 pdf_info.fModified.fDateTime = now; |
| 155 free); | 188 pdfa = true; |
| 156 assert(blob); | 189 #endif |
| 157 hb_blob_make_immutable(blob); | 190 return SkDocument::MakePDF(wStream, SK_ScalarDefaultRasterDPI, pdf_info, |
| 158 hb_face_t* face = hb_face_create(blob, (unsigned)index); | 191 nullptr, pdfa); |
| 159 hb_blob_destroy(blob); | 192 } |
| 160 assert(face); | 193 |
| 161 if (!face) { | 194 int main(int argc, char **argv) { |
| 162 fSkiaTypeface.reset(); | 195 Config config(argc, argv); |
| 163 return; | 196 SkFILEWStream wStream(config.output_file_name.value.c_str()); |
| 164 } | 197 sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream); |
| 165 hb_face_set_index(face, (unsigned)index); | 198 assert(doc); |
| 166 hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm()); | 199 Placement placement(&config, doc.get()); |
| 167 fHarfBuzzFace.reset(face); | 200 |
| 168 } | 201 const std::string &font_file = config.font_file.value; |
| 169 Face(const char* path, int index) { | 202 sk_sp<SkTypeface> typeface; |
| 170 // fairly portable mmap impl | |
| 171 auto data = SkData::MakeFromFileName(path); | |
| 172 assert(data); | |
| 173 if (!data) { return; } | |
| 174 fSkiaTypeface = SkTypeface::MakeFromStream(new SkMemoryStream(data), index); | |
| 175 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 | |
| 200 class Placement { | |
| 201 public: | |
| 202 Placement(Config &_config, SkWStream* outputStream) : config(_config) { | |
| 203 const std::string& font_file = config.font_file->value; | |
| 204 if (font_file.size() > 0) { | 203 if (font_file.size() > 0) { |
| 205 face = new Face(font_file.c_str(), 0 /* index */); | 204 typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */); |
| 206 } else { | 205 } |
| 207 face = new Face(SkTypeface::MakeDefault()); | 206 SkShaper shaper(typeface); |
| 208 } | 207 assert(shaper.good()); |
| 209 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); | |
| 239 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 | |
| 275 private: | |
| 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_he
ight->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, glyp
h_paint); | |
| 324 return true; | |
| 325 } // end of DrawGlyphs | |
| 326 }; // end of Placement class | |
| 327 | |
| 328 int 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);) { | 208 for (std::string line; std::getline(std::cin, line);) { |
| 333 placement.WriteLine(line.c_str()); | 209 placement.WriteLine(shaper, line.c_str(), line.size()); |
| 334 } | 210 } |
| 335 placement.Close(); | 211 |
| 336 | 212 doc->close(); |
| 337 return 0; | 213 return 0; |
| 338 } | 214 } |
| OLD | NEW |