| /* | |
| * 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 <cassert> | |
| #include <cstdlib> | |
| #include <iostream> | |
| #include <map> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #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/docs/SkPDFJpegHelpers.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<SkUnicode> 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<BaseOption*> &, int argc, char **argv); | |
| }; | |
| template <class T> | |
| 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<BaseOption*> &option_list, | |
| int argc, char **argv) { | |
| std::map<std::string, BaseOption *> 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<double> { | |
| 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<double>(std::move(_selector), | |
| std::move(_description), | |
| std::move(defaultValue)) {} | |
| }; | |
| struct StringOption : Option<std::string> { | |
| 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::string>(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<BaseOption*>{ | |
| &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<SkShaper::BiDiRunIterator> bidi = | |
| SkShapers::unicode::BidiRunIterator(get_unicode(), text, textBytes, defaultLevel); | |
| SkASSERT(bidi); | |
| std::unique_ptr<SkShaper::LanguageRunIterator> language = | |
| SkShaper::MakeStdLanguageRunIterator(text, textBytes); | |
| SkASSERT(language); | |
| std::unique_ptr<SkShaper::ScriptRunIterator> script = | |
| SkShapers::HB::ScriptRunIterator(text, textBytes); | |
| SkASSERT(script); | |
| std::unique_ptr<SkShaper::FontRunIterator> 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<const SkTextBlob> 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<SkDocument> 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 | |
| pdf_info.jpegDecoder = SkPDF::JPEG::Decode; | |
| pdf_info.jpegEncoder = SkPDF::JPEG::Encode; | |
| 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<SkDocument> doc = MakePDFDocument(config, &wStream); | |
| assert(doc); | |
| Placement placement(&config, doc.get()); | |
| const std::string &font_file = config.font_file.value; | |
| sk_sp<SkTypeface> typeface; | |
| if (font_file.size() > 0) { | |
| // There are different font managers for different platforms. See include/ports | |
| sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty(); | |
| assert(mgr); | |
| typeface = mgr->makeFromFile(font_file.c_str(), 0 /* index */); | |
| } | |
| std::unique_ptr<SkShaper> 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; | |
| } |