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 28struct BaseOption { 29 std::string selector; 30 std::string description; 31 virtual void set(std::string _value) = 0; 32 virtual std::string valueToString() = 0; 33 34 BaseOption(std::string _selector, std::string _description) 35 : selector(_selector), description(_description) {} 36 37 virtual ~BaseOption() {} 38 39 static void Init(const std::vector<BaseOption*> &, int argc, char **argv); 40}; 41 42template <class T> 43struct Option : BaseOption { 44 T value; 45 Option(std::string selector, std::string description, T defaultValue) 46 : BaseOption(selector, description), value(defaultValue) {} 47}; 48 49void 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 79struct DoubleOption : Option<double> { 80 virtual void set(std::string _value) { value = atof(_value.c_str()); } 81 virtual std::string valueToString() { 82 std::ostringstream stm; 83 stm << value; 84 return stm.str(); 85 } 86 DoubleOption(std::string selector, 87 std::string description, 88 double defaultValue) 89 : Option<double>(selector, description, defaultValue) {} 90}; 91 92struct StringOption : Option<std::string> { 93 virtual void set(std::string _value) { value = _value; } 94 virtual std::string valueToString() { return value; } 95 StringOption(std::string selector, 96 std::string description, 97 std::string defaultValue) 98 : Option<std::string>(selector, description, defaultValue) {} 99}; 100 101// Config ////////////////////////////////////////////////////////////////////// 102 103struct 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", 0.25f); 116 StringOption output_file_name = 117 StringOption("-o", ".pdf output file name", "out-skiahf.pdf"); 118 119 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 129class Placement { 130public: 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 140 void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) { 141 SkTextBlobBuilder textBlobBuilder; 142 SkPoint endPoint = shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, true, 143 SkPoint{0, 0}, 144 config->page_width.value - 2*config->left_margin.value); 145 sk_sp<const SkTextBlob> blob = textBlobBuilder.make(); 146 // If we don't have a page, or if we're not at the start of the page and the blob won't fit 147 if (!pageCanvas || 148 (current_y > config->line_spacing_ratio.value * config->font_size.value && 149 current_y + endPoint.y() > config->page_height.value) 150 ) { 151 if (pageCanvas) { 152 document->endPage(); 153 } 154 pageCanvas = document->beginPage( 155 SkDoubleToScalar(config->page_width.value), 156 SkDoubleToScalar(config->page_height.value)); 157 pageCanvas->drawPaint(white_paint); 158 current_x = config->left_margin.value; 159 current_y = config->line_spacing_ratio.value * config->font_size.value; 160 } 161 pageCanvas->drawTextBlob( 162 blob.get(), SkDoubleToScalar(current_x), 163 SkDoubleToScalar(current_y), glyph_paint); 164 // Advance to the next line. 165 current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value; 166 } 167 168private: 169 const Config* config; 170 SkDocument *document; 171 SkCanvas *pageCanvas; 172 SkPaint white_paint; 173 SkPaint glyph_paint; 174 double current_x; 175 double current_y; 176}; 177 178//////////////////////////////////////////////////////////////////////////////// 179 180static sk_sp<SkDocument> MakePDFDocument(const Config &config, SkWStream *wStream) { 181 SkDocument::PDFMetadata pdf_info; 182 pdf_info.fTitle = config.title.value.c_str(); 183 pdf_info.fAuthor = config.author.value.c_str(); 184 pdf_info.fSubject = config.subject.value.c_str(); 185 pdf_info.fKeywords = config.keywords.value.c_str(); 186 pdf_info.fCreator = config.creator.value.c_str(); 187 #if 0 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 pdf_info.fPDFA = true; 195 #endif 196 return SkDocument::MakePDF(wStream, pdf_info); 197} 198 199int main(int argc, char **argv) { 200 Config config(argc, argv); 201 SkFILEWStream wStream(config.output_file_name.value.c_str()); 202 sk_sp<SkDocument> doc = MakePDFDocument(config, &wStream); 203 assert(doc); 204 Placement placement(&config, doc.get()); 205 206 const std::string &font_file = config.font_file.value; 207 sk_sp<SkTypeface> typeface; 208 if (font_file.size() > 0) { 209 typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */); 210 } 211 SkShaper shaper(typeface); 212 assert(shaper.good()); 213 //SkString line("This is هذا هو الخط a line."); 214 //SkString line("This is a line هذا هو الخط."); 215 for (std::string line; std::getline(std::cin, line);) { 216 placement.WriteLine(shaper, line.c_str(), line.size()); 217 } 218 219 doc->close(); 220 wStream.flush(); 221 return 0; 222} 223