1/*
2 * Copyright (C) Research In Motion Limited 2010, 2012. All rights reserved.
3 * Copyright (C) 2015 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "config.h"
22#include "SVGPathUtilities.h"
23
24#include "FloatPoint.h"
25#include "Path.h"
26#include "PathTraversalState.h"
27#include "SVGPathBlender.h"
28#include "SVGPathBuilder.h"
29#include "SVGPathByteStreamBuilder.h"
30#include "SVGPathByteStreamSource.h"
31#include "SVGPathConsumer.h"
32#include "SVGPathElement.h"
33#include "SVGPathParser.h"
34#include "SVGPathSegListBuilder.h"
35#include "SVGPathSegListSource.h"
36#include "SVGPathStringBuilder.h"
37#include "SVGPathStringSource.h"
38#include "SVGPathTraversalStateBuilder.h"
39
40namespace WebCore {
41
42Path buildPathFromString(const String& d)
43{
44 if (d.isEmpty())
45 return { };
46
47 Path path;
48 SVGPathBuilder builder(path);
49 SVGPathStringSource source(d);
50 SVGPathParser::parse(source, builder);
51 return path;
52}
53
54String buildStringFromPath(const Path& path)
55{
56 StringBuilder builder;
57
58 if (!path.isNull() && !path.isEmpty()) {
59 path.apply([&builder] (const PathElement& element) {
60 switch (element.type) {
61 case PathElementMoveToPoint:
62 builder.append('M');
63 builder.appendShortestFormNumber(element.points[0].x());
64 builder.append(' ');
65 builder.appendShortestFormNumber(element.points[0].y());
66 break;
67 case PathElementAddLineToPoint:
68 builder.append('L');
69 builder.appendShortestFormNumber(element.points[0].x());
70 builder.append(' ');
71 builder.appendShortestFormNumber(element.points[0].y());
72 break;
73 case PathElementAddQuadCurveToPoint:
74 builder.append('Q');
75 builder.appendShortestFormNumber(element.points[0].x());
76 builder.append(' ');
77 builder.appendShortestFormNumber(element.points[0].y());
78 builder.append(',');
79 builder.appendShortestFormNumber(element.points[1].x());
80 builder.append(' ');
81 builder.appendShortestFormNumber(element.points[1].y());
82 break;
83 case PathElementAddCurveToPoint:
84 builder.append('C');
85 builder.appendShortestFormNumber(element.points[0].x());
86 builder.append(' ');
87 builder.appendShortestFormNumber(element.points[0].y());
88 builder.append(',');
89 builder.appendShortestFormNumber(element.points[1].x());
90 builder.append(' ');
91 builder.appendShortestFormNumber(element.points[1].y());
92 builder.append(',');
93 builder.appendShortestFormNumber(element.points[2].x());
94 builder.append(' ');
95 builder.appendShortestFormNumber(element.points[2].y());
96 break;
97 case PathElementCloseSubpath:
98 builder.append('Z');
99 break;
100 }
101 });
102 }
103
104 return builder.toString();
105}
106
107bool buildSVGPathByteStreamFromSVGPathSegList(const SVGPathSegList& list, SVGPathByteStream& stream, PathParsingMode parsingMode, bool checkForInitialMoveTo)
108{
109 stream.clear();
110 if (list.isEmpty())
111 return true;
112
113 SVGPathSegListSource source(list);
114 return SVGPathParser::parseToByteStream(source, stream, parsingMode, checkForInitialMoveTo);
115}
116
117Path buildPathFromByteStream(const SVGPathByteStream& stream)
118{
119 if (stream.isEmpty())
120 return { };
121
122 Path path;
123 SVGPathBuilder builder(path);
124 SVGPathByteStreamSource source(stream);
125 SVGPathParser::parse(source, builder);
126 return path;
127}
128
129bool buildSVGPathSegListFromByteStream(const SVGPathByteStream& stream, SVGPathSegList& list, PathParsingMode mode)
130{
131 if (stream.isEmpty())
132 return true;
133
134 SVGPathSegListBuilder builder(list);
135 SVGPathByteStreamSource source(stream);
136 return SVGPathParser::parse(source, builder, mode);
137}
138
139bool buildStringFromByteStream(const SVGPathByteStream& stream, String& result, PathParsingMode parsingMode, bool checkForInitialMoveTo)
140{
141 if (stream.isEmpty())
142 return true;
143
144 SVGPathByteStreamSource source(stream);
145 return SVGPathParser::parseToString(source, result, parsingMode, checkForInitialMoveTo);
146}
147
148bool buildSVGPathByteStreamFromString(const String& d, SVGPathByteStream& result, PathParsingMode parsingMode)
149{
150 result.clear();
151 if (d.isEmpty())
152 return true;
153
154 SVGPathStringSource source(d);
155 return SVGPathParser::parseToByteStream(source, result, parsingMode);
156}
157
158bool canBlendSVGPathByteStreams(const SVGPathByteStream& fromStream, const SVGPathByteStream& toStream)
159{
160 SVGPathByteStreamSource fromSource(fromStream);
161 SVGPathByteStreamSource toSource(toStream);
162 return SVGPathBlender::canBlendPaths(fromSource, toSource);
163}
164
165bool buildAnimatedSVGPathByteStream(const SVGPathByteStream& fromStream, const SVGPathByteStream& toStream, SVGPathByteStream& result, float progress)
166{
167 ASSERT(&toStream != &result);
168 result.clear();
169 if (toStream.isEmpty())
170 return true;
171
172 SVGPathByteStreamBuilder builder(result);
173
174 SVGPathByteStreamSource fromSource(fromStream);
175 SVGPathByteStreamSource toSource(toStream);
176 return SVGPathBlender::blendAnimatedPath(fromSource, toSource, builder, progress);
177}
178
179bool addToSVGPathByteStream(SVGPathByteStream& streamToAppendTo, const SVGPathByteStream& byStream, unsigned repeatCount)
180{
181 // The byStream will be blended with streamToAppendTo. So streamToAppendTo has to have elements.
182 if (streamToAppendTo.isEmpty() || byStream.isEmpty())
183 return true;
184
185 // builder is the destination of blending fromSource and bySource. The stream of builder
186 // (i.e. streamToAppendTo) has to be cleared before calling addAnimatedPath.
187 SVGPathByteStreamBuilder builder(streamToAppendTo);
188
189 SVGPathByteStream fromStreamCopy = WTFMove(streamToAppendTo);
190
191 SVGPathByteStreamSource fromSource(fromStreamCopy);
192 SVGPathByteStreamSource bySource(byStream);
193 return SVGPathBlender::addAnimatedPath(fromSource, bySource, builder, repeatCount);
194}
195
196bool getSVGPathSegAtLengthFromSVGPathByteStream(const SVGPathByteStream& stream, float length, unsigned& pathSeg)
197{
198 if (stream.isEmpty())
199 return false;
200
201 PathTraversalState traversalState(PathTraversalState::Action::SegmentAtLength);
202 SVGPathTraversalStateBuilder builder(traversalState, length);
203
204 SVGPathByteStreamSource source(stream);
205 bool ok = SVGPathParser::parse(source, builder);
206 pathSeg = builder.pathSegmentIndex();
207 return ok;
208}
209
210bool getTotalLengthOfSVGPathByteStream(const SVGPathByteStream& stream, float& totalLength)
211{
212 if (stream.isEmpty())
213 return false;
214
215 PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
216
217 SVGPathTraversalStateBuilder builder(traversalState);
218
219 SVGPathByteStreamSource source(stream);
220 bool ok = SVGPathParser::parse(source, builder);
221 totalLength = builder.totalLength();
222 return ok;
223}
224
225bool getPointAtLengthOfSVGPathByteStream(const SVGPathByteStream& stream, float length, FloatPoint& point)
226{
227 if (stream.isEmpty())
228 return false;
229
230 PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength);
231
232 SVGPathTraversalStateBuilder builder(traversalState, length);
233
234 SVGPathByteStreamSource source(stream);
235 bool ok = SVGPathParser::parse(source, builder);
236 point = builder.currentPoint();
237 return ok;
238}
239
240}
241