/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ // This sample progam demonstrates how to use Skia and HarfBuzz to // produce a PDF file from UTF-8 text in stdin. #include #include #include #include #include #include #include #include "include/core/SkCanvas.h" #include "include/core/SkStream.h" #include "include/core/SkTextBlob.h" #include "include/core/SkTypeface.h" #include "include/docs/SkPDFDocument.h" #include "include/ports/SkFontMgr_empty.h" #include "modules/skshaper/include/SkShaper_harfbuzz.h" #include "modules/skshaper/include/SkShaper_skunicode.h" #include "modules/skunicode/include/SkUnicode.h" namespace { sk_sp get_unicode() { #if defined(SK_UNICODE_ICU_IMPLEMENTATION) auto unicode = SkUnicodes::ICU::Make(); if (unicode) { return unicode; } #endif SkDEBUGFAIL("Only ICU implementation of SkUnicode is supported"); return nullptr; } } // namespace // Options ///////////////////////////////////////////////////////////////////// struct BaseOption { std::string selector; std::string description; virtual void set(const std::string& _value) = 0; virtual std::string valueToString() = 0; BaseOption(std::string _selector, std::string _description) : selector(std::move(_selector)) , description(std::move(_description)) {} virtual ~BaseOption() {} static void Init(const std::vector &, int argc, char **argv); }; template struct Option : BaseOption { T value; Option(std::string _selector, std::string _description, T defaultValue) : BaseOption(std::move(_selector), std::move(_description)) , value(defaultValue) {} }; void BaseOption::Init(const std::vector &option_list, int argc, char **argv) { std::map options; for (BaseOption *opt : option_list) { options[opt->selector] = opt; } for (int i = 1; i < argc; i++) { std::string option_selector(argv[i]); auto it = options.find(option_selector); if (it != options.end()) { if (i >= argc) { break; } const char *option_value = argv[i + 1]; it->second->set(option_value); i++; } else { printf("Ignoring unrecognized option: %s.\n", argv[i]); printf("Usage: %s {option value}\n", argv[0]); printf("\tTakes text from stdin and produces pdf file.\n"); printf("Supported options:\n"); for (BaseOption *opt : option_list) { printf("\t%s\t%s (%s)\n", opt->selector.c_str(), opt->description.c_str(), opt->valueToString().c_str()); } exit(-1); } } } struct DoubleOption : Option { void set(const std::string& _value) override { value = atof(_value.c_str()); } std::string valueToString() override { std::ostringstream stm; stm << value; return stm.str(); } DoubleOption(std::string _selector, std::string _description, double defaultValue) : Option(std::move(_selector), std::move(_description), std::move(defaultValue)) {} }; struct StringOption : Option { void set(const std::string& _value) override { value = _value; } std::string valueToString() override { return value; } StringOption(std::string _selector, std::string _description, std::string defaultValue) : Option(std::move(_selector), std::move(_description), std::move(defaultValue)) {} }; // Config ////////////////////////////////////////////////////////////////////// struct Config { DoubleOption page_width = DoubleOption("-w", "Page width", 600.0f); DoubleOption page_height = DoubleOption("-h", "Page height", 800.0f); StringOption title = StringOption("-t", "PDF title", "---"); StringOption author = StringOption("-a", "PDF author", "---"); StringOption subject = StringOption("-k", "PDF subject", "---"); StringOption keywords = StringOption("-c", "PDF keywords", "---"); StringOption creator = StringOption("-t", "PDF creator", "---"); StringOption font_file = StringOption("-f", ".ttf font file", ""); DoubleOption font_size = DoubleOption("-z", "Font size", 8.0f); DoubleOption left_margin = DoubleOption("-m", "Left margin", 20.0f); DoubleOption line_spacing_ratio = DoubleOption("-h", "Line spacing ratio", 0.25f); StringOption output_file_name = StringOption("-o", ".pdf output file name", "out-skiahf.pdf"); Config(int argc, char **argv) { BaseOption::Init(std::vector{ &page_width, &page_height, &title, &author, &subject, &keywords, &creator, &font_file, &font_size, &left_margin, &line_spacing_ratio, &output_file_name}, argc, argv); } }; // Placement /////////////////////////////////////////////////////////////////// class Placement { public: Placement(const Config* conf, SkDocument *doc) : config(conf), document(doc), pageCanvas(nullptr) { white_paint.setColor(SK_ColorWHITE); glyph_paint.setColor(SK_ColorBLACK); glyph_paint.setAntiAlias(true); font.setEdging(SkFont::Edging::kSubpixelAntiAlias); font.setSubpixel(true); font.setSize(SkDoubleToScalar(config->font_size.value)); } void WriteLine(const SkShaper& shaper, const char* text, size_t textBytes) { SkTextBlobBuilderRunHandler textBlobBuilder(text, {0, 0}); const SkBidiIterator::Level defaultLevel = SkBidiIterator::kLTR; std::unique_ptr bidi = SkShapers::unicode::BidiRunIterator(get_unicode(), text, textBytes, defaultLevel); SkASSERT(bidi); std::unique_ptr language = SkShaper::MakeStdLanguageRunIterator(text, textBytes); SkASSERT(language); std::unique_ptr script = SkShapers::HB::ScriptRunIterator(text, textBytes); SkASSERT(script); std::unique_ptr fontRuns = SkShaper::MakeFontMgrRunIterator(text, textBytes, font, SkFontMgr::RefEmpty()); SkASSERT(fontRuns); const SkScalar width = config->page_width.value - 2 * config->left_margin.value; shaper.shape(text, textBytes, *fontRuns, *bidi, *script, *language, nullptr, 0, width, &textBlobBuilder); SkPoint endPoint = textBlobBuilder.endPoint(); sk_sp blob = textBlobBuilder.makeBlob(); // If we don't have a page, or if we're not at the start of the page and the blob won't fit if (!pageCanvas || (current_y > config->line_spacing_ratio.value * config->font_size.value && current_y + endPoint.y() > config->page_height.value) ) { if (pageCanvas) { document->endPage(); } pageCanvas = document->beginPage( SkDoubleToScalar(config->page_width.value), SkDoubleToScalar(config->page_height.value)); pageCanvas->drawPaint(white_paint); current_x = config->left_margin.value; current_y = config->line_spacing_ratio.value * config->font_size.value; } pageCanvas->drawTextBlob( blob.get(), SkDoubleToScalar(current_x), SkDoubleToScalar(current_y), glyph_paint); // Advance to the next line. current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value; } private: const Config* config; SkDocument *document; SkCanvas *pageCanvas; SkPaint white_paint; SkPaint glyph_paint; SkFont font; double current_x; double current_y; }; //////////////////////////////////////////////////////////////////////////////// static sk_sp MakePDFDocument(const Config &config, SkWStream *wStream) { SkPDF::Metadata pdf_info; pdf_info.fTitle = config.title.value.c_str(); pdf_info.fAuthor = config.author.value.c_str(); pdf_info.fSubject = config.subject.value.c_str(); pdf_info.fKeywords = config.keywords.value.c_str(); pdf_info.fCreator = config.creator.value.c_str(); #if 0 SkPDF::DateTime now; SkPDFUtils::GetDateTime(&now); pdf_info.fCreation = now; pdf_info.fModified = now; pdf_info.fPDFA = true; #endif return SkPDF::MakeDocument(wStream, pdf_info); } int main(int argc, char **argv) { Config config(argc, argv); SkFILEWStream wStream(config.output_file_name.value.c_str()); sk_sp doc = MakePDFDocument(config, &wStream); assert(doc); Placement placement(&config, doc.get()); const std::string &font_file = config.font_file.value; sk_sp typeface; if (font_file.size() > 0) { // There are different font managers for different platforms. See include/ports sk_sp mgr = SkFontMgr_New_Custom_Empty(); assert(mgr); typeface = mgr->makeFromFile(font_file.c_str(), 0 /* index */); } std::unique_ptr shaper = SkShapers::HB::ShaperDrivenWrapper(get_unicode(), nullptr); assert(shaper); //SkString line("This is هذا هو الخط a line."); //SkString line("⁧This is a line هذا هو الخط.⁩"); for (std::string line; std::getline(std::cin, line);) { placement.WriteLine(*shaper, line.c_str(), line.size()); } doc->close(); wStream.flush(); return 0; }