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);
+ });
+}
+