tools/SkShaper: factor shaping out of using_skia_and_harfbuzz
authorhalcanary <halcanary@google.com>
Mon, 11 Jul 2016 21:30:39 +0000 (14:30 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 11 Jul 2016 21:30:39 +0000 (14:30 -0700)
Make a clearer demarcation between shaping and layout.

also: refactor using_skia_and_harfbuzz; stop leaking
like a sieve.

Review-Url: https://codereview.chromium.org/2138133002

gyp/tools.gyp
tools/SkShaper.cpp [new file with mode: 0644]
tools/SkShaper.h [new file with mode: 0644]
tools/using_skia_and_harfbuzz.cpp

index e2f0bd89e2a6ad97da3a2f1dd3e9a457191321a2..1904d09201f2d8760ef33d32f70b5b79720431c0 100644 (file)
     {
       'target_name': 'using_skia_and_harfbuzz',
       'type': 'executable',
-      'sources': [ '../tools/using_skia_and_harfbuzz.cpp' ],
+      'sources': [
+        '../tools/using_skia_and_harfbuzz.cpp',
+        '../tools/SkShaper.cpp',
+      ],
       'dependencies': [
         'skia_lib.gyp:skia_lib',
         'pdf.gyp:pdf',
diff --git a/tools/SkShaper.cpp b/tools/SkShaper.cpp
new file mode 100644 (file)
index 0000000..377f07c
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <hb-ot.h>
+
+#include "SkShaper.h"
+#include "SkStream.h"
+#include "SkTextBlob.h"
+#include "SkTypeface.h"
+
+static const int FONT_SIZE_SCALE = 512;
+
+struct SkShaper::Impl {
+    struct HBFontDel {
+        void operator()(hb_font_t* f) { hb_font_destroy(f); }
+    };
+    std::unique_ptr<hb_font_t, HBFontDel> fHarfBuzzFont;
+    struct HBBufDel {
+        void operator()(hb_buffer_t* b) { hb_buffer_destroy(b); }
+    };
+    std::unique_ptr<hb_buffer_t, HBBufDel> fBuffer;
+    sk_sp<SkTypeface> fTypeface;
+};
+
+SkShaper::SkShaper(sk_sp<SkTypeface> tf) : fImpl(new Impl) {
+    fImpl->fTypeface = tf ? std::move(tf) : SkTypeface::MakeDefault();
+    int index;
+    std::unique_ptr<SkStreamAsset> asset(fImpl->fTypeface->openStream(&index));
+    size_t size = asset->getLength();
+    SkAutoMalloc autoMalloc(size);  // TODO(halcanary): Avoid this malloc+copy.
+    asset->read(autoMalloc.get(), size);
+    asset = nullptr;
+    void* ptr = autoMalloc.get();
+    hb_blob_t* blob = hb_blob_create((char*)autoMalloc.release(), size,
+                                     HB_MEMORY_MODE_READONLY, ptr, sk_free);
+    SkASSERT(blob);
+    hb_blob_make_immutable(blob);
+
+    struct HBFaceDel {
+        void operator()(hb_face_t* f) { hb_face_destroy(f); }
+    };
+    std::unique_ptr<hb_face_t, HBFaceDel> face(hb_face_create(blob, (unsigned)index));
+    hb_blob_destroy(blob);
+    SkASSERT(face);
+    if (!face) {
+        return;
+    }
+    hb_face_set_index(face.get(), (unsigned)index);
+    hb_face_set_upem(face.get(), fImpl->fTypeface->getUnitsPerEm());
+
+    fImpl->fHarfBuzzFont.reset(hb_font_create(face.get()));
+    SkASSERT(fImpl->fHarfBuzzFont);
+    hb_font_set_scale(fImpl->fHarfBuzzFont.get(), FONT_SIZE_SCALE, FONT_SIZE_SCALE);
+    hb_ot_font_set_funcs(fImpl->fHarfBuzzFont.get());
+
+    fImpl->fBuffer.reset(hb_buffer_create());
+}
+
+SkShaper::~SkShaper() {}
+
+bool SkShaper::good() const { return fImpl->fHarfBuzzFont != nullptr; }
+
+SkScalar SkShaper::shape(SkTextBlobBuilder* builder,
+                         const SkPaint& srcPaint,
+                         const char* utf8text,
+                         size_t textBytes,
+                         SkPoint point) const {
+    SkPaint paint(srcPaint);
+    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    paint.setTypeface(fImpl->fTypeface);
+
+    SkASSERT(builder);
+    hb_buffer_t* buffer = fImpl->fBuffer.get();
+    hb_buffer_add_utf8(buffer, utf8text, -1, 0, -1);
+    hb_buffer_guess_segment_properties(buffer);
+    hb_shape(fImpl->fHarfBuzzFont.get(), buffer, nullptr, 0);
+    unsigned len = hb_buffer_get_length(buffer);
+    if (len == 0) {
+        hb_buffer_clear_contents(buffer);
+        return 0;
+    }
+
+    hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, NULL);
+    hb_glyph_position_t* pos =
+            hb_buffer_get_glyph_positions(buffer, NULL);
+    auto runBuffer = builder->allocRunPos(paint, len);
+
+    double x = point.x();
+    double y = point.y();
+
+    double textSizeY = paint.getTextSize() / (double)FONT_SIZE_SCALE;
+    double textSizeX = textSizeY * paint.getTextScaleX();
+
+    for (unsigned i = 0; i < len; i++) {
+        runBuffer.glyphs[i] = info[i].codepoint;
+        reinterpret_cast<SkPoint*>(runBuffer.pos)[i] =
+                SkPoint::Make(x + pos[i].x_offset * textSizeX,
+                              y - pos[i].y_offset * textSizeY);
+        x += pos[i].x_advance * textSizeX;
+        y += pos[i].y_advance * textSizeY;
+    }
+    hb_buffer_clear_contents(buffer);
+    return (SkScalar)x;
+}
diff --git a/tools/SkShaper.h b/tools/SkShaper.h
new file mode 100644 (file)
index 0000000..5aa93a7
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkShaper_DEFINED
+#define SkShaper_DEFINED
+
+#include <memory>
+
+#include "SkPoint.h"
+#include "SkTypeface.h"
+
+class SkPaint;
+class SkTextBlobBuilder;
+
+/**
+   Shapes text using harfbuzz and places the shaped text into a
+   TextBlob.
+ */
+class SkShaper {
+public:
+    SkShaper(sk_sp<SkTypeface> face);
+    ~SkShaper();
+
+    bool good() const;
+    SkScalar shape(SkTextBlobBuilder* dest,
+                   const SkPaint& srcPaint,
+                   const char* utf8text,
+                   size_t textBytes,
+                   SkPoint point) const;
+
+private:
+    SkShaper(const SkShaper&) = delete;
+    SkShaper& operator=(const SkShaper&) = delete;
+
+    struct Impl;
+    std::unique_ptr<Impl> fImpl;
+};
+
+#endif  // SkShaper_DEFINED
index 01a5b026895e4f5c009b246d9db96c253ffc0537..782103465eaea72916a629e7a6ddff80c3a04f9b 100644 (file)
 #include <cstdlib>
 #include <iostream>
 #include <map>
-#include <string>
 #include <sstream>
-
-#include <hb-ot.h>
+#include <string>
+#include <vector>
 
 #include "SkCanvas.h"
 #include "SkDocument.h"
+#include "SkShaper.h"
 #include "SkStream.h"
 #include "SkTextBlob.h"
 #include "SkTypeface.h"
 
-struct BaseOption {
-  std::string selector;
-  std::string description;
-  virtual void set(std::string _value) = 0;
-  virtual std::string valueToString() = 0;
+// Options /////////////////////////////////////////////////////////////////////
 
-  BaseOption(std::string _selector, std::string _description) :
-    selector(_selector),
-    description(_description) {}
-
-  virtual ~BaseOption() {}
-};
+struct BaseOption {
+    std::string selector;
+    std::string description;
+    virtual void set(std::string _value) = 0;
+    virtual std::string valueToString() = 0;
 
-template <class T> struct Option : BaseOption {
-  T value;
-  Option(std::string selector, std::string description, T defaultValue) :
-    BaseOption(selector, description),
-    value(defaultValue) {}
-};
+    BaseOption(std::string _selector, std::string _description)
+        : selector(_selector), description(_description) {}
 
-struct DoubleOption : Option<double> {
-  virtual void set(std::string _value) {
-    value = atof(_value.c_str());
-  }
-  virtual std::string valueToString() {
-      std::ostringstream stm;
-      stm << value;
-      return stm.str();
-  }
-  DoubleOption(std::string selector, std::string description, double defaultValue) :
-    Option<double>(selector, description, defaultValue) {}
-};
+    virtual ~BaseOption() {}
 
-struct SkStringOption : Option<SkString> {
-  virtual void set(std::string _value) {
-    value = _value.c_str();
-  }
-  virtual std::string valueToString() {
-    return value.c_str();
-  }
-  SkStringOption(std::string selector, std::string description, SkString defaultValue) :
-    Option<SkString>(selector, description, defaultValue) {}
+    static void Init(const std::vector<BaseOption*> &, int argc, char **argv);
 };
 
-struct StdStringOption : Option<std::string> {
-  virtual void set(std::string _value) {
-    value = _value;
-  }
-  virtual std::string valueToString() {
-    return value;
-  }
-  StdStringOption(std::string selector, std::string description, std::string defaultValue) :
-    Option<std::string>(selector, description, defaultValue) {}
+template <class T>
+struct Option : BaseOption {
+    T value;
+    Option(std::string selector, std::string description, T defaultValue)
+        : BaseOption(selector, description), value(defaultValue) {}
 };
 
-struct Config {
-  DoubleOption *page_width = new DoubleOption("-w", "Page width", 600.0f);
-  DoubleOption *page_height = new DoubleOption("-h", "Page height", 800.0f);
-  SkStringOption *title = new SkStringOption("-t", "PDF title", SkString("---"));
-  SkStringOption *author = new SkStringOption("-a", "PDF author", SkString("---"));
-  SkStringOption *subject = new SkStringOption("-k", "PDF subject", SkString("---"));
-  SkStringOption *keywords = new SkStringOption("-c", "PDF keywords", SkString("---"));
-  SkStringOption *creator = new SkStringOption("-t", "PDF creator", SkString("---"));
-  StdStringOption *font_file = new StdStringOption("-f", ".ttf font file", "");
-  DoubleOption *font_size = new DoubleOption("-z", "Font size", 8.0f);
-  DoubleOption *left_margin = new DoubleOption("-m", "Left margin", 20.0f);
-  DoubleOption *line_spacing_ratio = new DoubleOption("-h", "Line spacing ratio", 1.5f);
-  StdStringOption *output_file_name = new StdStringOption("-o", ".pdf output file name", "out-skiahf.pdf");
-
-  std::map<std::string, BaseOption*> options = {
-    { page_width->selector, page_width },
-    { page_height->selector, page_height },
-    { title->selector, title },
-    { author->selector, author },
-    { subject->selector, subject },
-    { keywords->selector, keywords },
-    { creator->selector, creator },
-    { font_file->selector, font_file },
-    { font_size->selector, font_size },
-    { left_margin->selector, left_margin },
-    { line_spacing_ratio->selector, line_spacing_ratio },
-    { output_file_name->selector, output_file_name },
-  };
-
-  Config(int argc, char **argv) {
+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;
+        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);
         }
-        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 (auto it = options.begin(); it != options.end(); ++it) {
-          printf("\t%s\t%s (%s)\n", it->first.c_str(),
-            it->second->description.c_str(),
-            it->second->valueToString().c_str());
-        }
-        exit(-1);
-      }
     }
-  } // end of Config::Config
+}
+
+struct DoubleOption : Option<double> {
+    virtual void set(std::string _value) { value = atof(_value.c_str()); }
+    virtual std::string valueToString() {
+        std::ostringstream stm;
+        stm << value;
+        return stm.str();
+    }
+    DoubleOption(std::string selector,
+                 std::string description,
+                 double defaultValue)
+        : Option<double>(selector, description, defaultValue) {}
 };
 
-const double FONT_SIZE_SCALE = 64.0f;
+struct StringOption : Option<std::string> {
+    virtual void set(std::string _value) { value = _value; }
+    virtual std::string valueToString() { return value; }
+    StringOption(std::string selector,
+                 std::string description,
+                 std::string defaultValue)
+        : Option<std::string>(selector, description, defaultValue) {}
+};
 
-struct Face {
-  struct HBFDel { void operator()(hb_face_t* f) { hb_face_destroy(f); } };
-  std::unique_ptr<hb_face_t, HBFDel> fHarfBuzzFace;
-  sk_sp<SkTypeface> fSkiaTypeface;
+// Config //////////////////////////////////////////////////////////////////////
 
-  Face(sk_sp<SkTypeface> skiaTypeface) : fSkiaTypeface(std::move(skiaTypeface)) {
-    int index;
-    std::unique_ptr<SkStreamAsset> asset(fSkiaTypeface->openStream(&index));
-    size_t size = asset->getLength();
-    // TODO(halcanary): avoid this malloc and copy.
-    char* buffer = (char*)malloc(size);
-    asset->read(buffer, size);
-    hb_blob_t* blob = hb_blob_create(buffer,
-                                     size,
-                                     HB_MEMORY_MODE_READONLY,
-                                     nullptr,
-                                     free);
-    assert(blob);
-    hb_blob_make_immutable(blob);
-    hb_face_t* face = hb_face_create(blob, (unsigned)index);
-    hb_blob_destroy(blob);
-    assert(face);
-    if (!face) {
-        fSkiaTypeface.reset();
-        return;
-    }
-    hb_face_set_index(face, (unsigned)index);
-    hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm());
-    fHarfBuzzFace.reset(face);
-  }
-  Face(const char* path, int index) {
-    // fairly portable mmap impl
-    auto data = SkData::MakeFromFileName(path);
-    assert(data);
-    if (!data) { return; }
-    fSkiaTypeface = SkTypeface::MakeFromStream(new SkMemoryStream(data), index);
-    assert(fSkiaTypeface);
-    if (!fSkiaTypeface) { return; }
-    auto destroy = [](void *d) { static_cast<SkData*>(d)->unref(); };
-    const char* bytes = (const char*)data->data();
-    unsigned int size = (unsigned int)data->size();
-    hb_blob_t* blob = hb_blob_create(bytes,
-                                     size,
-                                     HB_MEMORY_MODE_READONLY,
-                                     data.release(),
-                                     destroy);
-    assert(blob);
-    hb_blob_make_immutable(blob);
-    hb_face_t* face = hb_face_create(blob, (unsigned)index);
-    hb_blob_destroy(blob);
-    assert(face);
-    if (!face) {
-        fSkiaTypeface.reset();
-        return;
+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", 1.5f);
+    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);
     }
-    hb_face_set_index(face, (unsigned)index);
-    hb_face_set_upem(face, fSkiaTypeface->getUnitsPerEm());
-    fHarfBuzzFace.reset(face);
-  }
 };
 
+// Placement ///////////////////////////////////////////////////////////////////
+
 class Placement {
- public:
-  Placement(Config &_config, SkWStream* outputStream) : config(_config) {
-    const std::string& font_file = config.font_file->value;
-    if (font_file.size() > 0) {
-      face = new Face(font_file.c_str(), 0 /* index */);
-    } else {
-      face = new Face(SkTypeface::MakeDefault());
+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.setFlags(SkPaint::kAntiAlias_Flag |
+                             SkPaint::kSubpixelText_Flag);
+        glyph_paint.setTextSize(config->font_size.value);
     }
-    hb_font = hb_font_create(face->fHarfBuzzFace.get());
-
-    hb_font_set_scale(hb_font,
-        FONT_SIZE_SCALE * config.font_size->value,
-        FONT_SIZE_SCALE * config.font_size->value);
-    hb_ot_font_set_funcs(hb_font);
-
-    SkDocument::PDFMetadata pdf_info;
-    pdf_info.fTitle = config.title->value;
-    pdf_info.fAuthor = config.author->value;
-    pdf_info.fSubject = config.subject->value;
-    pdf_info.fKeywords = config.keywords->value;
-    pdf_info.fCreator = config.creator->value;
-    SkTime::DateTime now;
-    SkTime::GetDateTime(&now);
-    pdf_info.fCreation.fEnabled = true;
-    pdf_info.fCreation.fDateTime = now;
-    pdf_info.fModified.fEnabled = true;
-    pdf_info.fModified.fDateTime = now;
-    pdfDocument = SkDocument::MakePDF(outputStream, SK_ScalarDefaultRasterDPI,
-                                      pdf_info, nullptr, true);
-    assert(pdfDocument);
 
-    white_paint.setColor(SK_ColorWHITE);
-
-    glyph_paint.setFlags(
-        SkPaint::kAntiAlias_Flag |
-        SkPaint::kSubpixelText_Flag);  // ... avoid waggly text when rotating.
-    glyph_paint.setColor(SK_ColorBLACK);
-    glyph_paint.setTextSize(config.font_size->value);
-    glyph_paint.setTypeface(face->fSkiaTypeface);
-    glyph_paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-
-    NewPage();
-  } // end of Placement
-
-  ~Placement() {
-    delete face;
-    hb_font_destroy (hb_font);
-  }
-
-  void WriteLine(const char *text) {
-    /* Create hb-buffer and populate. */
-    hb_buffer_t *hb_buffer = hb_buffer_create ();
-    hb_buffer_add_utf8 (hb_buffer, text, -1, 0, -1);
-    hb_buffer_guess_segment_properties (hb_buffer);
-
-    /* Shape it! */
-    hb_shape (hb_font, hb_buffer, NULL, 0);
-
-    DrawGlyphs(hb_buffer);
-
-    hb_buffer_destroy (hb_buffer);
+    void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
+        if (!pageCanvas || current_y > config->page_height.value) {
+            if (pageCanvas) {
+                document->endPage();
+            }
+            pageCanvas = document->beginPage(config->page_width.value,
+                                             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;
+        }
+        SkTextBlobBuilder textBlobBuilder;
+        shaper.shape(&textBlobBuilder, glyph_paint, text, textBytes, SkPoint{0, 0});
+        sk_sp<const SkTextBlob> blob(textBlobBuilder.build());
+        pageCanvas->drawTextBlob(blob.get(), current_x, current_y, glyph_paint);
 
-    // Advance to the next line.
-    current_y += config.line_spacing_ratio->value * config.font_size->value;
-    if (current_y > config.page_height->value) {
-      pdfDocument->endPage();
-      NewPage();
+        // Advance to the next line.
+        current_y += config->line_spacing_ratio.value * config->font_size.value;
     }
-  }
-
-  bool Close() {
-    return pdfDocument->close();
-  }
 
 private:
-  Config config;
-
-  Face *face;
-
-  hb_font_t *hb_font;
-
-  sk_sp<SkDocument> pdfDocument;
-
-  SkCanvas* pageCanvas;
-
-  SkPaint white_paint;
-  SkPaint glyph_paint;
-
-  double current_x;
-  double current_y;
-
-  void NewPage() {
-    pageCanvas = pdfDocument->beginPage(config.page_width->value, 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;
-  }
-
-  bool DrawGlyphs(hb_buffer_t *hb_buffer) {
-    SkTextBlobBuilder textBlobBuilder;
-    unsigned len = hb_buffer_get_length (hb_buffer);
-    if (len == 0) {
-      return true;
-    }
-    hb_glyph_info_t *info = hb_buffer_get_glyph_infos (hb_buffer, NULL);
-    hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (hb_buffer, NULL);
-    auto runBuffer = textBlobBuilder.allocRunPos(glyph_paint, len);
+    const Config* config;
+    SkDocument *document;
+    SkCanvas *pageCanvas;
+    SkPaint white_paint;
+    SkPaint glyph_paint;
+    double current_x;
+    double current_y;
+};
 
-    double x = 0;
-    double y = 0;
-    for (unsigned int i = 0; i < len; i++)
-    {
-      runBuffer.glyphs[i] = info[i].codepoint;
-      reinterpret_cast<SkPoint*>(runBuffer.pos)[i] = SkPoint::Make(
-        x + pos[i].x_offset / FONT_SIZE_SCALE,
-        y - pos[i].y_offset / FONT_SIZE_SCALE);
-      x += pos[i].x_advance / FONT_SIZE_SCALE;
-      y += pos[i].y_advance / FONT_SIZE_SCALE;
-    }
+////////////////////////////////////////////////////////////////////////////////
 
-    pageCanvas->drawTextBlob(textBlobBuilder.build(), current_x, current_y, glyph_paint);
-    return true;
-  } // end of DrawGlyphs
-}; // end of Placement class
+static sk_sp<SkDocument> MakePDFDocument(const Config &config,
+                                         SkWStream *wStream) {
+    SkDocument::PDFMetadata 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();
+    bool pdfa = false;
+    #if 0
+        SkTime::DateTime now;
+        SkTime::GetDateTime(&now);
+        pdf_info.fCreation.fEnabled = true;
+        pdf_info.fCreation.fDateTime = now;
+        pdf_info.fModified.fEnabled = true;
+        pdf_info.fModified.fDateTime = now;
+        pdfa = true;
+    #endif
+    return SkDocument::MakePDF(wStream, SK_ScalarDefaultRasterDPI, pdf_info,
+                               nullptr, pdfa);
+}
 
-int main(int argc, char** argv) {
+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());
 
-    Placement placement(config, new SkFILEWStream(config.output_file_name->value.c_str()));
+    const std::string &font_file = config.font_file.value;
+    sk_sp<SkTypeface> typeface;
+    if (font_file.size() > 0) {
+        typeface = SkTypeface::MakeFromFile(font_file.c_str(), 0 /* index */);
+    }
+    SkShaper shaper(typeface);
+    assert(shaper.good());
     for (std::string line; std::getline(std::cin, line);) {
-      placement.WriteLine(line.c_str());
+        placement.WriteLine(shaper, line.c_str(), line.size());
     }
-    placement.Close();
 
+    doc->close();
     return 0;
 }