|
| 1 | +#include <SVGPath.h> |
| 2 | +#include <reanimated/CSS/svg/values/SVGPath.h> |
| 3 | + |
| 4 | +#include <reanimated/CSS/common/transforms/vectors.h> |
| 5 | + |
| 6 | +#include <cstddef> |
| 7 | +#include <functional> |
| 8 | +#include <optional> |
| 9 | +#include <string> |
| 10 | + |
| 11 | +namespace reanimated::css { |
| 12 | + |
| 13 | +using Point = Vector2D; |
| 14 | +using Cubic = std::array<Vector2D, 4>; |
| 15 | +using SubPath = SVGPath::SubPath; |
| 16 | + |
| 17 | +SVGPath::SVGPath() : subPaths() {} |
| 18 | + |
| 19 | +SVGPath::SVGPath(std::vector<SubPath> &&subPaths) : subPaths(std::move(subPaths)) {} |
| 20 | + |
| 21 | +SVGPath::SVGPath(jsi::Runtime &rt, const jsi::Value &jsiValue) : SVGPath(jsiValue.getString(rt).utf8(rt)) {} |
| 22 | + |
| 23 | +SVGPath::SVGPath(const folly::dynamic &value) : SVGPath(value.getString()) {} |
| 24 | + |
| 25 | +bool SVGPath::canConstruct(jsi::Runtime &rt, const jsi::Value &jsiValue) { |
| 26 | + return jsiValue.isString(); |
| 27 | +} |
| 28 | + |
| 29 | +bool SVGPath::canConstruct(const folly::dynamic &value) { |
| 30 | + return value.isString(); |
| 31 | +} |
| 32 | + |
| 33 | +folly::dynamic SVGPath::toDynamic() const { |
| 34 | + return toString(); |
| 35 | +} |
| 36 | + |
| 37 | +std::string SVGPath::toString() const { |
| 38 | + std::ostringstream oss; |
| 39 | + oss << std::defaultfloat; |
| 40 | + for (size_t i = 0; i < subPaths.size(); ++i) { |
| 41 | + const auto &segment = subPaths[i]; |
| 42 | + |
| 43 | + if (i > 0) { |
| 44 | + oss << " "; |
| 45 | + } |
| 46 | + |
| 47 | + oss << "M" << segment.M[0] << " " << segment.M[1]; |
| 48 | + |
| 49 | + for (const auto &point : segment.C) { |
| 50 | + oss << " C"; |
| 51 | + for (int j = 1; j < 4; ++j) { |
| 52 | + // In SVGPath C command start point is implicit |
| 53 | + oss << " " << point[j][0] << " " << point[j][1]; |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + if (segment.Z) { |
| 58 | + oss << " Z"; |
| 59 | + } |
| 60 | + } |
| 61 | + return oss.str(); |
| 62 | +} |
| 63 | + |
| 64 | +SVGPath SVGPath::interpolate(const double progress, const SVGPath &to) const { |
| 65 | + const auto &longerPath = (subPaths.size() >= to.subPaths.size()) ? subPaths : to.subPaths; |
| 66 | + const auto &shorterPath = (subPaths.size() < to.subPaths.size()) ? subPaths : to.subPaths; |
| 67 | + |
| 68 | + size_t longerSize = longerPath.size(); |
| 69 | + size_t shorterSize = shorterPath.size(); |
| 70 | + |
| 71 | + std::vector<std::reference_wrapper<const SubPath>> fromRef; |
| 72 | + std::vector<std::reference_wrapper<const SubPath>> toRef; |
| 73 | + |
| 74 | + fromRef.reserve(longerSize); |
| 75 | + toRef.reserve(longerSize); |
| 76 | + |
| 77 | + if (shorterSize == 0) { |
| 78 | + return to; |
| 79 | + } |
| 80 | + |
| 81 | + size_t baseGroupSize = longerSize / shorterSize; |
| 82 | + size_t remainder = longerSize % shorterSize; |
| 83 | + |
| 84 | + size_t longerPathIndex = 0; |
| 85 | + |
| 86 | + for (size_t i = 0; i < shorterSize; ++i) { |
| 87 | + size_t currentGroupSize = baseGroupSize + (i < remainder ? 1 : 0); |
| 88 | + |
| 89 | + for (size_t j = 0; j < currentGroupSize; ++j) { |
| 90 | + if (subPaths.size() <= to.subPaths.size()) { |
| 91 | + fromRef.push_back(std::cref(subPaths[i])); |
| 92 | + toRef.push_back(std::cref(to.subPaths[longerPathIndex])); |
| 93 | + } else { |
| 94 | + fromRef.push_back(std::cref(subPaths[longerPathIndex])); |
| 95 | + toRef.push_back(std::cref(to.subPaths[i])); |
| 96 | + } |
| 97 | + longerPathIndex++; |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + std::vector<SubPath> interpolatedSubPaths; |
| 102 | + interpolatedSubPaths.reserve(longerSize); |
| 103 | + |
| 104 | + for (size_t i = 0; i < longerSize; ++i) { |
| 105 | + interpolatedSubPaths.push_back(interpolateSubPaths(fromRef[i], toRef[i], progress)); |
| 106 | + } |
| 107 | + |
| 108 | + return SVGPath(std::move(interpolatedSubPaths)); |
| 109 | +} |
| 110 | + |
| 111 | +bool SVGPath::operator==(const SVGPath &other) const { |
| 112 | + return toString() == other.toString(); |
| 113 | +} |
| 114 | + |
| 115 | +#ifndef NDEBUG |
| 116 | + |
| 117 | +std::ostream &operator<<(std::ostream &os, const SVGPath &value) { |
| 118 | + os << "SVGPath(" << value.toString() << ")"; |
| 119 | + return os; |
| 120 | +} |
| 121 | + |
| 122 | +#endif // NDEBUG |
| 123 | + |
| 124 | +std::vector<SubPath> SVGPath::parseSVGPath(const std::string &value) const { |
| 125 | + std::vector<SubPath> result; |
| 126 | + std::stringstream ss(value); |
| 127 | + Point currPos; |
| 128 | + |
| 129 | + // Format of input: (M num num |C num num num num num num |Z)* |
| 130 | + while (ss >> std::ws && !ss.eof()) { |
| 131 | + char cmd; |
| 132 | + ss >> cmd; |
| 133 | + |
| 134 | + switch (cmd) { |
| 135 | + case 'M': |
| 136 | + double x, y; |
| 137 | + ss >> x >> y; |
| 138 | + result.emplace_back(Point(x, y)); |
| 139 | + currPos = Point(x, y); |
| 140 | + break; |
| 141 | + case 'C': |
| 142 | + if (!result.empty()) { |
| 143 | + Point p0(currPos), p1, p2, p3; |
| 144 | + ss >> p1[0] >> p1[1] >> p2[0] >> p2[1] >> p3[0] >> p3[1]; |
| 145 | + result.back().C.push_back({p0, p1, p2, p3}); |
| 146 | + currPos = p3; |
| 147 | + break; |
| 148 | + } |
| 149 | + // Fallthrough |
| 150 | + case 'Z': |
| 151 | + if (!result.empty()) { |
| 152 | + result.back().Z = true; |
| 153 | + currPos = result.back().M; |
| 154 | + break; |
| 155 | + } |
| 156 | + // Fallthrough |
| 157 | + default: |
| 158 | + std::invalid_argument("[Reanimated] Invalid SVGPath string format."); |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + return result; |
| 163 | +} |
| 164 | + |
| 165 | +std::vector<Cubic> SVGPath::splitCubic(Cubic cubic, int count) const { |
| 166 | + std::vector<Cubic> result; |
| 167 | + for (int i = 0; i < count; i++) { |
| 168 | + double t = 1.0 / (count - i); |
| 169 | + auto [st, nd] = singleSplitCubic(cubic, t); |
| 170 | + result.push_back(st); |
| 171 | + cubic = nd; |
| 172 | + } |
| 173 | + return result; |
| 174 | +} |
| 175 | + |
| 176 | +SubPath SVGPath::interpolateSubPaths(const SubPath &from, const SubPath &to, double t) const { |
| 177 | + Point newM = from.M.interpolate(t, to.M); |
| 178 | + SubPath result(newM); |
| 179 | + |
| 180 | + result.Z = to.Z; |
| 181 | + |
| 182 | + size_t longerSize = std::max(from.C.size(), to.C.size()); |
| 183 | + size_t shorterSize = std::min(from.C.size(), to.C.size()); |
| 184 | + |
| 185 | + if (shorterSize == 0) { |
| 186 | + return result; |
| 187 | + } |
| 188 | + |
| 189 | + size_t baseGroupSize = longerSize / shorterSize; |
| 190 | + size_t remainder = longerSize % shorterSize; |
| 191 | + |
| 192 | + std::vector<Cubic> prolongatedShorter; |
| 193 | + prolongatedShorter.reserve(longerSize); |
| 194 | + |
| 195 | + for (size_t i = 0; i < shorterSize; ++i) { |
| 196 | + size_t currentGroupSize = baseGroupSize + (i < remainder ? 1 : 0); |
| 197 | + std::vector<Cubic> x = |
| 198 | + from.C.size() <= to.C.size() ? splitCubic(from.C[i], currentGroupSize) : splitCubic(to.C[i], currentGroupSize); |
| 199 | + prolongatedShorter.insert(prolongatedShorter.end(), x.begin(), x.end()); |
| 200 | + } |
| 201 | + |
| 202 | + auto &fromRef = from.C.size() <= to.C.size() ? prolongatedShorter : from.C; |
| 203 | + auto &toRef = from.C.size() <= to.C.size() ? to.C : prolongatedShorter; |
| 204 | + |
| 205 | + for (size_t i = 0; i < longerSize; ++i) { |
| 206 | + const auto &c1 = fromRef[i]; |
| 207 | + const auto &c2 = toRef[i]; |
| 208 | + |
| 209 | + Cubic newCubic; |
| 210 | + |
| 211 | + for (size_t j = 0; j < 4; ++j) { |
| 212 | + newCubic[j] = c1[j].interpolate(t, c2[j]); |
| 213 | + } |
| 214 | + |
| 215 | + // ensure continuity |
| 216 | + if (i == 0) { |
| 217 | + newCubic[0] = result.M; |
| 218 | + } else { |
| 219 | + newCubic[0] = result.C.back()[3]; |
| 220 | + } |
| 221 | + |
| 222 | + // Ensure tangent differs from 0 |
| 223 | + { |
| 224 | + constexpr double NUDGE_EPS = 5e-1; |
| 225 | + newCubic[1] = applyDirectionalNudge(newCubic[1], newCubic[0], newCubic[2], newCubic[3], NUDGE_EPS); |
| 226 | + newCubic[2] = applyDirectionalNudge(newCubic[2], newCubic[3], newCubic[1], newCubic[0], NUDGE_EPS); |
| 227 | + } |
| 228 | + |
| 229 | + result.C.push_back(newCubic); |
| 230 | + } |
| 231 | + |
| 232 | + return result; |
| 233 | +} |
| 234 | + |
| 235 | +Point SVGPath::lineAt(Point p0, Point p1, double t) const { |
| 236 | + return Point(p0[0] + t * (p1[0] - p0[0]), p0[1] + t * (p1[1] - p0[1])); |
| 237 | +} |
| 238 | + |
| 239 | +Point SVGPath::applyDirectionalNudge( |
| 240 | + Point target, |
| 241 | + const Point &anchor, |
| 242 | + const Point &guide, |
| 243 | + const Point &altGuide, |
| 244 | + double epsilon) const { |
| 245 | + double dx0 = target[0] - anchor[0]; |
| 246 | + double dy0 = target[1] - anchor[1]; |
| 247 | + |
| 248 | + if (dx0 * dx0 + dy0 * dy0 < epsilon * epsilon) { |
| 249 | + |
| 250 | + Point v = Point(guide[0] - anchor[0], guide[1] - anchor[1]); |
| 251 | + double vLen = v.length(); |
| 252 | + |
| 253 | + if (vLen < epsilon) { |
| 254 | + v = Point(altGuide[0] - anchor[0], altGuide[1] - anchor[1]); |
| 255 | + vLen = v.length(); |
| 256 | + } |
| 257 | + |
| 258 | + if (vLen < epsilon) { |
| 259 | + target[0] += epsilon; |
| 260 | + return target; |
| 261 | + } |
| 262 | + |
| 263 | + v.normalize(); |
| 264 | + return Point(target[0] + v[0] * epsilon, target[1] + v[1] * epsilon); |
| 265 | + } |
| 266 | + |
| 267 | + return target; |
| 268 | +} |
| 269 | + |
| 270 | +std::pair<Cubic, Cubic> SVGPath::singleSplitCubic(const Cubic &p, double t) const { |
| 271 | + Point p01 = lineAt(p[0], p[1], t); |
| 272 | + Point p12 = lineAt(p[1], p[2], t); |
| 273 | + Point p23 = lineAt(p[2], p[3], t); |
| 274 | + Point c0 = lineAt(p01, p12, t); |
| 275 | + Point c1 = lineAt(p12, p23, t); |
| 276 | + Point q = lineAt(c0, c1, t); |
| 277 | + |
| 278 | + return {{p[0], p01, c0, q}, {q, c1, p23, p[3]}}; |
| 279 | +} |
| 280 | + |
| 281 | +} // namespace reanimated::css |
0 commit comments