textonpath util

Bug: skia:7554
Change-Id: Ifff3030f9d7be24146b5230bbf0e47f73000adfd
Reviewed-on: https://skia-review.googlesource.com/141460
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/gn/utils.gni b/gn/utils.gni
index 68667be..fc050eb 100644
--- a/gn/utils.gni
+++ b/gn/utils.gni
@@ -60,6 +60,7 @@
   "$_src/utils/SkShadowTessellator.cpp",
   "$_src/utils/SkShadowTessellator.h",
   "$_src/utils/SkShadowUtils.cpp",
+  "$_src/utils/SkTextOnPath.cpp",
   "$_src/utils/SkThreadUtils_pthread.cpp",
   "$_src/utils/SkThreadUtils_win.cpp",
   "$_src/utils/SkUTF.cpp",
diff --git a/include/utils/SkTextOnPath.h b/include/utils/SkTextOnPath.h
new file mode 100644
index 0000000..0d2e050
--- /dev/null
+++ b/include/utils/SkTextOnPath.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTextOnPath_DEFINED
+#define SkTextOnPath_DEFINED
+
+#include "SkTypes.h"
+
+class SkCanvas;
+class SkMatrix;
+class SkPath;
+
+void SkVisitTextOnPath(const void* text, size_t byteLength, const SkPaint& paint,
+                       const SkPath& follow, const SkMatrix* matrix,
+                       const std::function<void(const SkPath&)>& visitor);
+
+void SkDrawTextOnPath(const void* text, size_t byteLength, const SkPaint& paint,
+                      const SkPath& follow, const SkMatrix* matrix, SkCanvas* canvas);
+
+#endif
+
diff --git a/src/utils/SkTextOnPath.cpp b/src/utils/SkTextOnPath.cpp
new file mode 100644
index 0000000..3918ba4
--- /dev/null
+++ b/src/utils/SkTextOnPath.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPathMeasure.h"
+#include "SkCanvas.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+
+#include "SkTextToPathIter.h"
+
+static void morphpoints(SkPoint dst[], const SkPoint src[], int count,
+                        SkPathMeasure& meas, const SkMatrix& matrix) {
+    for (int i = 0; i < count; i++) {
+        SkPoint pos;
+        SkVector tangent;
+
+        matrix.mapXY(src[i].fX, src[i].fY, &pos);
+        SkScalar sx = pos.fX;
+        SkScalar sy = pos.fY;
+
+        if (!meas.getPosTan(sx, &pos, &tangent)) {
+            // set to 0 if the measure failed, so that we just set dst == pos
+            tangent.set(0, 0);
+        }
+
+        /*  This is the old way (that explains our approach but is way too slow
+         SkMatrix    matrix;
+         SkPoint     pt;
+
+         pt.set(sx, sy);
+         matrix.setSinCos(tangent.fY, tangent.fX);
+         matrix.preTranslate(-sx, 0);
+         matrix.postTranslate(pos.fX, pos.fY);
+         matrix.mapPoints(&dst[i], &pt, 1);
+         */
+        dst[i].set(pos.fX - tangent.fY * sy, pos.fY + tangent.fX * sy);
+    }
+}
+
+/*  TODO
+
+ Need differentially more subdivisions when the follow-path is curvy. Not sure how to
+ determine that, but we need it. I guess a cheap answer is let the caller tell us,
+ but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
+ */
+static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
+                      const SkMatrix& matrix) {
+    SkPath::Iter    iter(src, false);
+    SkPoint         srcP[4], dstP[3];
+    SkPath::Verb    verb;
+
+    while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case SkPath::kMove_Verb:
+                morphpoints(dstP, srcP, 1, meas, matrix);
+                dst->moveTo(dstP[0]);
+                break;
+            case SkPath::kLine_Verb:
+                // turn lines into quads to look bendy
+                srcP[0].fX = SkScalarAve(srcP[0].fX, srcP[1].fX);
+                srcP[0].fY = SkScalarAve(srcP[0].fY, srcP[1].fY);
+                morphpoints(dstP, srcP, 2, meas, matrix);
+                dst->quadTo(dstP[0], dstP[1]);
+                break;
+            case SkPath::kQuad_Verb:
+                morphpoints(dstP, &srcP[1], 2, meas, matrix);
+                dst->quadTo(dstP[0], dstP[1]);
+                break;
+            case SkPath::kConic_Verb:
+                morphpoints(dstP, &srcP[1], 2, meas, matrix);
+                dst->conicTo(dstP[0], dstP[1], iter.conicWeight());
+                break;
+            case SkPath::kCubic_Verb:
+                morphpoints(dstP, &srcP[1], 3, meas, matrix);
+                dst->cubicTo(dstP[0], dstP[1], dstP[2]);
+                break;
+            case SkPath::kClose_Verb:
+                dst->close();
+                break;
+            default:
+                SkDEBUGFAIL("unknown verb");
+                break;
+        }
+    }
+}
+
+void SkVisitTextOnPath(const void* text, size_t byteLength, const SkPaint& paint,
+                       const SkPath& follow, const SkMatrix* matrix,
+                       const std::function<void(const SkPath&)>& visitor) {
+    if (byteLength == 0) {
+        return;
+    }
+
+    SkTextToPathIter    iter((const char*)text, byteLength, paint, false);
+    SkPathMeasure       meas(follow, false);
+    SkScalar            hOffset = 0;
+
+    // need to measure first
+    if (paint.getTextAlign() != SkPaint::kLeft_Align) {
+        SkScalar pathLen = meas.getLength();
+        if (paint.getTextAlign() == SkPaint::kCenter_Align) {
+            pathLen = SkScalarHalf(pathLen);
+        }
+        hOffset += pathLen;
+    }
+
+    const SkPath*   iterPath;
+    SkScalar        xpos;
+    SkMatrix        scaledMatrix;
+    SkScalar        scale = iter.getPathScale();
+
+    scaledMatrix.setScale(scale, scale);
+
+    while (iter.next(&iterPath, &xpos)) {
+        if (iterPath) {
+            SkPath      tmp;
+            SkMatrix    m(scaledMatrix);
+
+            tmp.setIsVolatile(true);
+            m.postTranslate(xpos + hOffset, 0);
+            if (matrix) {
+                m.postConcat(*matrix);
+            }
+            morphpath(&tmp, *iterPath, meas, m);
+            visitor(tmp);
+        }
+    }
+}
+
+void SkDrawTextOnPath(const void* text, size_t byteLength, const SkPaint& paint,
+                      const SkPath& follow, const SkMatrix* matrix, SkCanvas* canvas) {
+    SkVisitTextOnPath(text, byteLength, paint, follow, matrix, [canvas, paint](const SkPath& path) {
+        canvas->drawPath(path, paint);
+    });
+}
+