blob: 03fcf300f80ab3fef67e065c95a17801fcd658f6 [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>
halcanarydd05e2a2016-05-12 10:09:58 -070015#include <sstream>
halcanary8a74f132016-07-11 14:30:39 -070016#include <string>
17#include <vector>
halcanary85e38bc2016-05-04 09:34:25 -070018
Mike Kleinc0bd9f92019-04-23 12:05:21 -050019#include "include/core/SkCanvas.h"
20#include "include/core/SkStream.h"
21#include "include/core/SkTextBlob.h"
22#include "include/core/SkTypeface.h"
23#include "include/docs/SkPDFDocument.h"
24#include "modules/skshaper/include/SkShaper.h"
halcanary00d44e02016-05-03 15:09:52 -070025
halcanary8a74f132016-07-11 14:30:39 -070026// Options /////////////////////////////////////////////////////////////////////
27
halcanary00d44e02016-05-03 15:09:52 -070028struct BaseOption {
halcanary8a74f132016-07-11 14:30:39 -070029 std::string selector;
30 std::string description;
31 virtual void set(std::string _value) = 0;
32 virtual std::string valueToString() = 0;
halcanary00d44e02016-05-03 15:09:52 -070033
halcanary8a74f132016-07-11 14:30:39 -070034 BaseOption(std::string _selector, std::string _description)
35 : selector(_selector), description(_description) {}
halcanary00d44e02016-05-03 15:09:52 -070036
halcanary8a74f132016-07-11 14:30:39 -070037 virtual ~BaseOption() {}
38
39 static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
halcanary00d44e02016-05-03 15:09:52 -070040};
41
halcanary8a74f132016-07-11 14:30:39 -070042template <class T>
43struct Option : BaseOption {
44 T value;
Ben Wagner95fc1192018-11-30 11:24:47 -050045 Option(std::string _selector, std::string _description, T defaultValue)
46 : BaseOption(_selector, _description), value(defaultValue) {}
halcanary00d44e02016-05-03 15:09:52 -070047};
48
halcanary8a74f132016-07-11 14:30:39 -070049void 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
halcanary00d44e02016-05-03 15:09:52 -070079struct DoubleOption : Option<double> {
halcanary8a74f132016-07-11 14:30:39 -070080 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 }
Ben Wagner95fc1192018-11-30 11:24:47 -050086 DoubleOption(std::string _selector,
87 std::string _description,
halcanary8a74f132016-07-11 14:30:39 -070088 double defaultValue)
Ben Wagner95fc1192018-11-30 11:24:47 -050089 : Option<double>(_selector, _description, defaultValue) {}
halcanary00d44e02016-05-03 15:09:52 -070090};
91
halcanary8a74f132016-07-11 14:30:39 -070092struct StringOption : Option<std::string> {
93 virtual void set(std::string _value) { value = _value; }
94 virtual std::string valueToString() { return value; }
Ben Wagner95fc1192018-11-30 11:24:47 -050095 StringOption(std::string _selector,
96 std::string _description,
halcanary8a74f132016-07-11 14:30:39 -070097 std::string defaultValue)
Ben Wagner95fc1192018-11-30 11:24:47 -050098 : Option<std::string>(_selector, _description, defaultValue) {}
halcanary00d44e02016-05-03 15:09:52 -070099};
100
halcanary8a74f132016-07-11 14:30:39 -0700101// Config //////////////////////////////////////////////////////////////////////
halcanary00d44e02016-05-03 15:09:52 -0700102
103struct Config {
halcanary8a74f132016-07-11 14:30:39 -0700104 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 =
Ben Wagner5d4dd8b2018-01-25 14:37:17 -0500115 DoubleOption("-h", "Line spacing ratio", 0.25f);
halcanary8a74f132016-07-11 14:30:39 -0700116 StringOption output_file_name =
117 StringOption("-o", ".pdf output file name", "out-skiahf.pdf");
halcanary00d44e02016-05-03 15:09:52 -0700118
halcanary8a74f132016-07-11 14:30:39 -0700119 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);
halcanary00d44e02016-05-03 15:09:52 -0700124 }
halcanary00d44e02016-05-03 15:09:52 -0700125};
126
halcanary8a74f132016-07-11 14:30:39 -0700127// Placement ///////////////////////////////////////////////////////////////////
halcanary00d44e02016-05-03 15:09:52 -0700128
129class Placement {
halcanary8a74f132016-07-11 14:30:39 -0700130public:
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);
Mike Reede5f9cfa2019-01-10 13:55:35 -0500135 glyph_paint.setAntiAlias(true);
Hal Canary2a1848d2018-11-26 17:23:24 -0500136 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
Mike Reede5f9cfa2019-01-10 13:55:35 -0500137 font.setSubpixel(true);
Hal Canary2a1848d2018-11-26 17:23:24 -0500138 font.setSize(SkDoubleToScalar(config->font_size.value));
halcanary627ad6d2016-07-01 08:48:12 -0700139 }
halcanary00d44e02016-05-03 15:09:52 -0700140
halcanary8a74f132016-07-11 14:30:39 -0700141 void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
Ben Wagner3bdb69c2019-04-01 19:01:09 -0400142 SkTextBlobBuilderRunHandler textBlobBuilder(text, {0, 0});
143 shaper.shape(text, textBytes, font, true,
144 config->page_width.value - 2*config->left_margin.value, &textBlobBuilder);
145 SkPoint endPoint = textBlobBuilder.endPoint();
Florin Malita9867f612018-12-12 10:54:49 -0500146 sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
Ben Wagner36a944f2018-01-29 15:45:30 -0500147 // If we don't have a page, or if we're not at the start of the page and the blob won't fit
148 if (!pageCanvas ||
149 (current_y > config->line_spacing_ratio.value * config->font_size.value &&
150 current_y + endPoint.y() > config->page_height.value)
151 ) {
halcanary8a74f132016-07-11 14:30:39 -0700152 if (pageCanvas) {
153 document->endPage();
154 }
halcanary13cba492016-08-03 10:43:55 -0700155 pageCanvas = document->beginPage(
156 SkDoubleToScalar(config->page_width.value),
157 SkDoubleToScalar(config->page_height.value));
halcanary8a74f132016-07-11 14:30:39 -0700158 pageCanvas->drawPaint(white_paint);
159 current_x = config->left_margin.value;
160 current_y = config->line_spacing_ratio.value * config->font_size.value;
161 }
halcanary13cba492016-08-03 10:43:55 -0700162 pageCanvas->drawTextBlob(
163 blob.get(), SkDoubleToScalar(current_x),
164 SkDoubleToScalar(current_y), glyph_paint);
halcanary8a74f132016-07-11 14:30:39 -0700165 // Advance to the next line.
Ben Wagner5d4dd8b2018-01-25 14:37:17 -0500166 current_y += endPoint.y() + config->line_spacing_ratio.value * config->font_size.value;
halcanary00d44e02016-05-03 15:09:52 -0700167 }
halcanary00d44e02016-05-03 15:09:52 -0700168
169private:
halcanary8a74f132016-07-11 14:30:39 -0700170 const Config* config;
171 SkDocument *document;
172 SkCanvas *pageCanvas;
173 SkPaint white_paint;
174 SkPaint glyph_paint;
Hal Canary2a1848d2018-11-26 17:23:24 -0500175 SkFont font;
halcanary8a74f132016-07-11 14:30:39 -0700176 double current_x;
177 double current_y;
178};
halcanary00d44e02016-05-03 15:09:52 -0700179
halcanary8a74f132016-07-11 14:30:39 -0700180////////////////////////////////////////////////////////////////////////////////
halcanary00d44e02016-05-03 15:09:52 -0700181
Mike Reeda4daf192017-12-14 13:25:04 -0500182static sk_sp<SkDocument> MakePDFDocument(const Config &config, SkWStream *wStream) {
Hal Canary23564b92018-09-07 14:33:14 -0400183 SkPDF::Metadata pdf_info;
halcanary8a74f132016-07-11 14:30:39 -0700184 pdf_info.fTitle = config.title.value.c_str();
185 pdf_info.fAuthor = config.author.value.c_str();
186 pdf_info.fSubject = config.subject.value.c_str();
187 pdf_info.fKeywords = config.keywords.value.c_str();
188 pdf_info.fCreator = config.creator.value.c_str();
halcanary8a74f132016-07-11 14:30:39 -0700189 #if 0
190 SkTime::DateTime now;
191 SkTime::GetDateTime(&now);
Hal Canary23564b92018-09-07 14:33:14 -0400192 pdf_info.fCreation = now;
193 pdf_info.fModified = now;
Mike Reeda4daf192017-12-14 13:25:04 -0500194 pdf_info.fPDFA = true;
halcanary8a74f132016-07-11 14:30:39 -0700195 #endif
Hal Canary23564b92018-09-07 14:33:14 -0400196 return SkPDF::MakeDocument(wStream, pdf_info);
halcanary8a74f132016-07-11 14:30:39 -0700197}
halcanary00d44e02016-05-03 15:09:52 -0700198
halcanary8a74f132016-07-11 14:30:39 -0700199int main(int argc, char **argv) {
halcanary00d44e02016-05-03 15:09:52 -0700200 Config config(argc, argv);
halcanary8a74f132016-07-11 14:30:39 -0700201 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());
halcanary00d44e02016-05-03 15:09:52 -0700205
halcanary8a74f132016-07-11 14:30:39 -0700206 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 */);
halcanary00d44e02016-05-03 15:09:52 -0700210 }
Ben Wagnerb0591942019-02-15 14:46:18 -0500211 std::unique_ptr<SkShaper> shaper = SkShaper::Make();
212 assert(shaper);
Ben Wagnera25fbef2017-08-30 13:56:19 -0400213 //SkString line("This is هذا هو الخط a line.");
Ben Wagner8d45a382017-11-16 10:08:28 -0500214 //SkString line("⁧This is a line هذا هو الخط.⁩");
halcanary8a74f132016-07-11 14:30:39 -0700215 for (std::string line; std::getline(std::cin, line);) {
Ben Wagnerb0591942019-02-15 14:46:18 -0500216 placement.WriteLine(*shaper, line.c_str(), line.size());
halcanary8a74f132016-07-11 14:30:39 -0700217 }
halcanary00d44e02016-05-03 15:09:52 -0700218
halcanary8a74f132016-07-11 14:30:39 -0700219 doc->close();
Ben Wagnera25fbef2017-08-30 13:56:19 -0400220 wStream.flush();
halcanary00d44e02016-05-03 15:09:52 -0700221 return 0;
222}