mirror of
https://github.com/GLEGram/GLEGram-iOS.git
synced 2026-05-01 23:35:13 +02:00
4647310322
Based on Swiftgram 12.5 (Telegram iOS 12.5). All GLEGram features ported and organized in GLEGram/ folder. Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass, Font Replacement, Fake Profile, Chat Export, Plugin System, and more. See CHANGELOG_12.5.md for full details.
752 lines
24 KiB
C++
752 lines
24 KiB
C++
#include <LottieCpp/BezierPath.h>
|
|
|
|
#if __APPLE__
|
|
#include <Accelerate/Accelerate.h>
|
|
#endif
|
|
|
|
#include "Lottie/Private/Parsing/JsonParsing.hpp"
|
|
|
|
namespace lottie {
|
|
|
|
BezierTrimPathPosition::BezierTrimPathPosition(float start_, float end_) :
|
|
start(start_),
|
|
end(end_) {
|
|
}
|
|
|
|
BezierPathContents::BezierPathContents(CurveVertex const &startPoint) :
|
|
elements({ PathElement(startPoint) }) {
|
|
}
|
|
|
|
BezierPathContents::BezierPathContents() :
|
|
elements({}),
|
|
closed(false) {
|
|
}
|
|
|
|
BezierPathContents::BezierPathContents(lottiejson11::Json const &jsonAny) noexcept(false) :
|
|
elements({}) {
|
|
lottiejson11::Json::object const *json = nullptr;
|
|
if (jsonAny.is_object()) {
|
|
json = &jsonAny.object_items();
|
|
} else if (jsonAny.is_array()) {
|
|
if (jsonAny.array_items().empty()) {
|
|
throw LottieParsingException();
|
|
}
|
|
if (!jsonAny.array_items()[0].is_object()) {
|
|
throw LottieParsingException();
|
|
}
|
|
json = &jsonAny.array_items()[0].object_items();
|
|
}
|
|
|
|
if (const auto closedData = getOptionalBool(*json, "c")) {
|
|
closed = closedData.value();
|
|
}
|
|
|
|
auto vertexContainer = getAnyArray(*json, "v");
|
|
auto inPointsContainer = getAnyArray(*json, "i");
|
|
auto outPointsContainer = getAnyArray(*json, "o");
|
|
|
|
if (vertexContainer.size() != inPointsContainer.size() || inPointsContainer.size() != outPointsContainer.size()) {
|
|
throw LottieParsingException();
|
|
}
|
|
if (vertexContainer.empty()) {
|
|
return;
|
|
}
|
|
|
|
/// Create first point
|
|
Vector2D firstPoint = Vector2D(vertexContainer[0]);
|
|
Vector2D firstInPoint = Vector2D(inPointsContainer[0]);
|
|
Vector2D firstOutPoint = Vector2D(outPointsContainer[0]);
|
|
CurveVertex firstVertex = CurveVertex::relative(
|
|
firstPoint,
|
|
firstInPoint,
|
|
firstOutPoint
|
|
);
|
|
PathElement previousElement(firstVertex);
|
|
elements.push_back(previousElement);
|
|
|
|
for (size_t i = 1; i < vertexContainer.size(); i++) {
|
|
Vector2D point = Vector2D(vertexContainer[i]);
|
|
Vector2D inPoint = Vector2D(inPointsContainer[i]);
|
|
Vector2D outPoint = Vector2D(outPointsContainer[i]);
|
|
CurveVertex vertex = CurveVertex::relative(
|
|
point,
|
|
inPoint,
|
|
outPoint
|
|
);
|
|
auto pathElement = previousElement.pathElementTo(vertex);
|
|
elements.push_back(pathElement);
|
|
previousElement = pathElement;
|
|
}
|
|
|
|
if (closed.value_or(false)) {
|
|
auto closeElement = previousElement.pathElementTo(firstVertex);
|
|
elements.push_back(closeElement);
|
|
}
|
|
}
|
|
|
|
lottiejson11::Json BezierPathContents::toJson() const {
|
|
lottiejson11::Json::object result;
|
|
|
|
lottiejson11::Json::array vertices;
|
|
lottiejson11::Json::array inPoints;
|
|
lottiejson11::Json::array outPoints;
|
|
|
|
for (const auto &element : elements) {
|
|
vertices.push_back(element.vertex.point.toJson());
|
|
inPoints.push_back(element.vertex.inTangentRelative().toJson());
|
|
outPoints.push_back(element.vertex.outTangentRelative().toJson());
|
|
}
|
|
|
|
result.insert(std::make_pair("v", vertices));
|
|
result.insert(std::make_pair("i", inPoints));
|
|
result.insert(std::make_pair("o", outPoints));
|
|
|
|
if (closed.has_value()) {
|
|
result.insert(std::make_pair("c", closed.value()));
|
|
}
|
|
|
|
return lottiejson11::Json(result);
|
|
}
|
|
|
|
/*std::shared_ptr<CGPath> BezierPathContents::cgPath() const {
|
|
auto cgPath = CGPath::makePath();
|
|
|
|
std::optional<PathElement> previousElement;
|
|
for (const auto &element : elements) {
|
|
if (previousElement.has_value()) {
|
|
if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
|
cgPath->addLineTo(element.vertex.point);
|
|
} else {
|
|
cgPath->addCurveTo(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent);
|
|
}
|
|
} else {
|
|
cgPath->moveTo(element.vertex.point);
|
|
}
|
|
previousElement = element;
|
|
}
|
|
if (closed.value_or(true)) {
|
|
cgPath->closeSubpath();
|
|
}
|
|
return cgPath;
|
|
}*/
|
|
|
|
float BezierPathContents::length() {
|
|
if (_length.has_value()) {
|
|
return _length.value();
|
|
} else {
|
|
float result = 0.0;
|
|
for (size_t i = 1; i < elements.size(); i++) {
|
|
result += elements[i].length(elements[i - 1]);
|
|
}
|
|
_length = result;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void BezierPathContents::moveToStartPoint(CurveVertex const &vertex) {
|
|
elements = { PathElement(vertex) };
|
|
_length = std::nullopt;
|
|
}
|
|
|
|
void BezierPathContents::addVertex(CurveVertex const &vertex) {
|
|
addElement(PathElement(vertex));
|
|
}
|
|
|
|
void BezierPathContents::reserveCapacity(size_t capacity) {
|
|
elements.reserve(capacity);
|
|
}
|
|
|
|
void BezierPathContents::setElementCount(size_t count) {
|
|
elements.resize(count, PathElement(CurveVertex::absolute(Vector2D(0.0, 0.0), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0))));
|
|
}
|
|
|
|
void BezierPathContents::invalidateLength() {
|
|
_length.reset();
|
|
}
|
|
|
|
void BezierPathContents::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
|
if (elements.empty()) {
|
|
return;
|
|
}
|
|
auto previous = elements[elements.size() - 1];
|
|
auto newVertex = CurveVertex::absolute(toPoint, inTangent, toPoint);
|
|
updateVertex(
|
|
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, outTangent),
|
|
(int)elements.size() - 1,
|
|
false
|
|
);
|
|
addVertex(newVertex);
|
|
}
|
|
|
|
void BezierPathContents::addLine(Vector2D const &toPoint) {
|
|
if (elements.empty()) {
|
|
return;
|
|
}
|
|
auto previous = elements[elements.size() - 1];
|
|
auto newVertex = CurveVertex::relative(toPoint, Vector2D::Zero(), Vector2D::Zero());
|
|
updateVertex(
|
|
CurveVertex::absolute(previous.vertex.point, previous.vertex.inTangent, previous.vertex.point),
|
|
(int)elements.size() - 1,
|
|
false
|
|
);
|
|
addVertex(newVertex);
|
|
}
|
|
|
|
void BezierPathContents::close() {
|
|
closed = true;
|
|
}
|
|
|
|
void BezierPathContents::addElement(PathElement const &pathElement) {
|
|
elements.push_back(pathElement);
|
|
}
|
|
|
|
void BezierPathContents::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
|
if (remeasure) {
|
|
PathElement newElement(CurveVertex::absolute(Vector2D::Zero(), Vector2D::Zero(), Vector2D::Zero()));
|
|
if (atIndex > 0) {
|
|
auto previousElement = elements[atIndex - 1];
|
|
newElement = previousElement.pathElementTo(vertex);
|
|
} else {
|
|
newElement = PathElement(vertex);
|
|
}
|
|
elements[atIndex] = newElement;
|
|
|
|
if (atIndex + 1 < elements.size()) {
|
|
auto nextElement = elements[atIndex + 1];
|
|
elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex);
|
|
}
|
|
|
|
} else {
|
|
auto oldElement = elements[atIndex];
|
|
elements[atIndex] = oldElement.updateVertex(vertex);
|
|
}
|
|
}
|
|
|
|
std::vector<std::shared_ptr<BezierPathContents>> BezierPathContents::trim(float fromLength, float toLength, float offsetLength) {
|
|
if (elements.size() <= 1) {
|
|
return {};
|
|
}
|
|
|
|
if (fromLength == toLength) {
|
|
return {};
|
|
}
|
|
|
|
float lengthValue = length();
|
|
|
|
/// Normalize lengths to the curve length.
|
|
auto start = fmod(fromLength + offsetLength, lengthValue);
|
|
auto end = fmod(toLength + offsetLength, lengthValue);
|
|
|
|
if (start < 0.0) {
|
|
start = lengthValue + start;
|
|
}
|
|
|
|
if (end < 0.0) {
|
|
end = lengthValue + end;
|
|
}
|
|
|
|
if (start == lengthValue) {
|
|
start = 0.0;
|
|
}
|
|
if (end == 0.0) {
|
|
end = lengthValue;
|
|
}
|
|
|
|
if (
|
|
(start == 0.0 && end == lengthValue) ||
|
|
start == end ||
|
|
(start == lengthValue && end == 0.0)
|
|
) {
|
|
/// The trim encompasses the entire path. Return.
|
|
return { shared_from_this() };
|
|
}
|
|
|
|
if (start > end) {
|
|
// Start is greater than end. Two paths are returned.
|
|
return trimPathAtLengths({
|
|
BezierTrimPathPosition(0.0, end),
|
|
BezierTrimPathPosition(start, lengthValue)
|
|
});
|
|
}
|
|
|
|
return trimPathAtLengths({ BezierTrimPathPosition(start, end) });
|
|
}
|
|
|
|
// MARK: Private
|
|
|
|
std::vector<std::shared_ptr<BezierPathContents>> BezierPathContents::trimPathAtLengths(std::vector<BezierTrimPathPosition> const &positions) {
|
|
if (positions.empty()) {
|
|
return {};
|
|
}
|
|
auto remainingPositions = positions;
|
|
|
|
auto trim = remainingPositions[0];
|
|
remainingPositions.erase(remainingPositions.begin());
|
|
|
|
std::vector<std::shared_ptr<BezierPathContents>> paths;
|
|
|
|
float runningLength = 0.0;
|
|
bool finishedTrimming = false;
|
|
auto pathElements = elements;
|
|
|
|
auto currentPath = std::make_shared<BezierPathContents>();
|
|
int i = 0;
|
|
|
|
while (!finishedTrimming) {
|
|
if (pathElements.size() <= i) {
|
|
/// Do this for rounding errors
|
|
paths.push_back(currentPath);
|
|
finishedTrimming = true;
|
|
continue;
|
|
}
|
|
/// Loop through and add elements within start->end range.
|
|
/// Get current element
|
|
auto element = pathElements[i];
|
|
float elementLength = 0.0;
|
|
if (i != 0) {
|
|
elementLength = element.length(pathElements[i - 1]);
|
|
}
|
|
|
|
/// Calculate new running length.
|
|
auto newLength = runningLength + elementLength;
|
|
|
|
if (newLength < trim.start) {
|
|
/// Element is not included in the trim, continue.
|
|
runningLength = newLength;
|
|
i = i + 1;
|
|
/// Increment index, we are done with this element.
|
|
continue;
|
|
}
|
|
|
|
if (newLength == trim.start) {
|
|
/// Current element IS the start element.
|
|
/// For start we want to add a zero length element.
|
|
currentPath->moveToStartPoint(element.vertex);
|
|
runningLength = newLength;
|
|
i = i + 1;
|
|
/// Increment index, we are done with this element.
|
|
continue;
|
|
}
|
|
|
|
if (runningLength < trim.start && trim.start < newLength && currentPath->elements.size() == 0) {
|
|
/// The start of the trim is between this element and the previous, trim.
|
|
/// Get previous element.
|
|
auto previousElement = pathElements[i - 1];
|
|
/// Trim it
|
|
auto trimLength = trim.start - runningLength;
|
|
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
|
/// Add the right span start.
|
|
currentPath->moveToStartPoint(trimResults.rightSpan.start.vertex);
|
|
|
|
pathElements[i] = trimResults.rightSpan.end;
|
|
pathElements[i - 1] = trimResults.rightSpan.start;
|
|
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
|
/// Dont increment index or the current length, the end of this path can be within this span.
|
|
continue;
|
|
}
|
|
|
|
if (trim.start < newLength && newLength < trim.end) {
|
|
/// Element lies within the trim span.
|
|
currentPath->addElement(element);
|
|
runningLength = newLength;
|
|
i = i + 1;
|
|
continue;
|
|
}
|
|
|
|
if (newLength == trim.end) {
|
|
/// Element is the end element.
|
|
/// The element could have a new length if it's added right after the start node.
|
|
currentPath->addElement(element);
|
|
/// We are done with this span.
|
|
runningLength = newLength;
|
|
i = i + 1;
|
|
/// Allow the path to be finalized.
|
|
/// Fall through to finalize path and move to next position
|
|
}
|
|
|
|
if (runningLength < trim.end && trim.end < newLength) {
|
|
/// New element must be cut for end.
|
|
/// Get previous element.
|
|
auto previousElement = pathElements[i - 1];
|
|
/// Trim it
|
|
auto trimLength = trim.end - runningLength;
|
|
auto trimResults = element.splitElementAtPosition(previousElement, trimLength);
|
|
/// Add the left span end.
|
|
|
|
currentPath->updateVertex(trimResults.leftSpan.start.vertex, (int)currentPath->elements.size() - 1, false);
|
|
currentPath->addElement(trimResults.leftSpan.end);
|
|
|
|
pathElements[i] = trimResults.rightSpan.end;
|
|
pathElements[i - 1] = trimResults.rightSpan.start;
|
|
runningLength = runningLength + trimResults.leftSpan.end.length(trimResults.leftSpan.start);
|
|
/// Dont increment index or the current length, the start of the next path can be within this span.
|
|
/// We are done with this span.
|
|
/// Allow the path to be finalized.
|
|
/// Fall through to finalize path and move to next position
|
|
}
|
|
|
|
paths.push_back(currentPath);
|
|
currentPath = std::make_shared<BezierPathContents>();
|
|
if (remainingPositions.size() > 0) {
|
|
trim = remainingPositions[0];
|
|
remainingPositions.erase(remainingPositions.begin());
|
|
} else {
|
|
finishedTrimming = true;
|
|
}
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
BezierPath::BezierPath(CurveVertex const &startPoint) :
|
|
_contents(std::make_shared<BezierPathContents>(startPoint)) {
|
|
}
|
|
|
|
BezierPath::BezierPath() :
|
|
_contents(std::make_shared<BezierPathContents>()) {
|
|
}
|
|
|
|
BezierPath::BezierPath(lottiejson11::Json const &jsonAny) noexcept(false) :
|
|
_contents(std::make_shared<BezierPathContents>(jsonAny)) {
|
|
}
|
|
|
|
lottiejson11::Json BezierPath::toJson() const {
|
|
return _contents->toJson();
|
|
}
|
|
|
|
float BezierPath::length() {
|
|
return _contents->length();
|
|
}
|
|
|
|
void BezierPath::moveToStartPoint(CurveVertex const &vertex) {
|
|
_contents->moveToStartPoint(vertex);
|
|
}
|
|
|
|
void BezierPath::addVertex(CurveVertex const &vertex) {
|
|
_contents->addVertex(vertex);
|
|
}
|
|
|
|
void BezierPath::reserveCapacity(size_t capacity) {
|
|
_contents->reserveCapacity(capacity);
|
|
}
|
|
|
|
void BezierPath::setElementCount(size_t count) {
|
|
_contents->setElementCount(count);
|
|
}
|
|
|
|
void BezierPath::invalidateLength() {
|
|
_contents->invalidateLength();
|
|
}
|
|
|
|
void BezierPath::addCurve(Vector2D const &toPoint, Vector2D const &outTangent, Vector2D const &inTangent) {
|
|
_contents->addCurve(toPoint, outTangent, inTangent);
|
|
}
|
|
|
|
void BezierPath::addLine(Vector2D const &toPoint) {
|
|
_contents->addLine(toPoint);
|
|
}
|
|
|
|
void BezierPath::close() {
|
|
_contents->close();
|
|
}
|
|
|
|
void BezierPath::addElement(PathElement const &pathElement) {
|
|
_contents->addElement(pathElement);
|
|
}
|
|
|
|
void BezierPath::updateVertex(CurveVertex const &vertex, int atIndex, bool remeasure) {
|
|
_contents->updateVertex(vertex, atIndex, remeasure);
|
|
}
|
|
|
|
std::vector<BezierPath> BezierPath::trim(float fromLength, float toLength, float offsetLength) {
|
|
std::vector<BezierPath> result;
|
|
|
|
auto resultContents = _contents->trim(fromLength, toLength, offsetLength);
|
|
for (const auto &resultContent : resultContents) {
|
|
result.emplace_back(resultContent);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<PathElement> const &BezierPath::elements() const {
|
|
return _contents->elements;
|
|
}
|
|
|
|
std::vector<PathElement> &BezierPath::mutableElements() {
|
|
return _contents->elements;
|
|
}
|
|
|
|
std::optional<bool> const &BezierPath::closed() const {
|
|
return _contents->closed;
|
|
}
|
|
void BezierPath::setClosed(std::optional<bool> const &closed) {
|
|
_contents->closed = closed;
|
|
}
|
|
|
|
/*std::shared_ptr<CGPath> BezierPath::cgPath() const {
|
|
return _contents->cgPath();
|
|
}*/
|
|
|
|
BezierPath BezierPath::copyUsingTransform(Transform2D const &transform) const {
|
|
if (transform == Transform2D::identity()) {
|
|
return (*this);
|
|
}
|
|
BezierPath result;
|
|
result._contents->closed = _contents->closed;
|
|
result.reserveCapacity(_contents->elements.size());
|
|
for (const auto &element : _contents->elements) {
|
|
result._contents->elements.emplace_back(element.vertex.transformed(transform));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
BezierPath::BezierPath(std::shared_ptr<BezierPathContents> contents) :
|
|
_contents(contents) {
|
|
}
|
|
|
|
BezierPathsBoundingBoxContext::BezierPathsBoundingBoxContext() :
|
|
pointsX((float *)malloc(1024 * 4)),
|
|
pointsY((float *)malloc(1024 * 4)),
|
|
pointsSize(1024) {
|
|
}
|
|
|
|
BezierPathsBoundingBoxContext::~BezierPathsBoundingBoxContext() {
|
|
free(pointsX);
|
|
free(pointsY);
|
|
}
|
|
|
|
static CGRect calculateBoundingRectOpt(float const *pointsX, float const *pointsY, int count) {
|
|
#if __APPLE__
|
|
float minX = 0.0;
|
|
float maxX = 0.0;
|
|
vDSP_minv(pointsX, 1, &minX, count);
|
|
vDSP_maxv(pointsX, 1, &maxX, count);
|
|
|
|
float minY = 0.0;
|
|
float maxY = 0.0;
|
|
vDSP_minv(pointsY, 1, &minY, count);
|
|
vDSP_maxv(pointsY, 1, &maxY, count);
|
|
|
|
return CGRect(minX, minY, maxX - minX, maxY - minY);
|
|
#else
|
|
float minX = pointsX[0];
|
|
float maxX = pointsX[0];
|
|
for (int i = 0; i < count; i++) {
|
|
minX = std::min(minX, pointsX[i]);
|
|
maxX = std::max(maxX, pointsX[i]);
|
|
}
|
|
|
|
float minY = pointsY[0];
|
|
float maxY = pointsY[0];
|
|
for (int i = 0; i < count; i++) {
|
|
minY = std::min(minY, pointsY[i]);
|
|
maxY = std::max(maxY, pointsY[i]);
|
|
}
|
|
|
|
return CGRect(minX, minY, maxX - minX, maxY - minY);
|
|
#endif
|
|
}
|
|
|
|
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector<BezierPath> const &paths) {
|
|
int pointCount = 0;
|
|
|
|
float *pointsX = context.pointsX;
|
|
float *pointsY = context.pointsY;
|
|
int pointsSize = context.pointsSize;
|
|
|
|
for (const auto &path : paths) {
|
|
PathElement const *pathElements = path.elements().data();
|
|
int pathElementCount = (int)path.elements().size();
|
|
|
|
for (int i = 0; i < pathElementCount; i++) {
|
|
const auto &element = pathElements[i];
|
|
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 1) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)element.vertex.point.x;
|
|
pointsY[pointCount] = (float)element.vertex.point.y;
|
|
pointCount++;
|
|
|
|
if (i != 0) {
|
|
const auto &previousElement = pathElements[i - 1];
|
|
if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
|
} else {
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 2) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)previousElement.vertex.outTangent.x;
|
|
pointsY[pointCount] = (float)previousElement.vertex.outTangent.y;
|
|
pointCount++;
|
|
pointsX[pointCount] = (float)element.vertex.inTangent.x;
|
|
pointsY[pointCount] = (float)element.vertex.inTangent.y;
|
|
pointCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
context.pointsX = pointsX;
|
|
context.pointsY = pointsY;
|
|
context.pointsSize = pointsSize;
|
|
|
|
if (pointCount == 0) {
|
|
return CGRect(0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
return calculateBoundingRectOpt(pointsX, pointsY, pointCount);
|
|
}
|
|
|
|
CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path) {
|
|
int pointCount = 0;
|
|
|
|
float *pointsX = context.pointsX;
|
|
float *pointsY = context.pointsY;
|
|
int pointsSize = context.pointsSize;
|
|
|
|
PathElement const *pathElements = path.elements().data();
|
|
int pathElementCount = (int)path.elements().size();
|
|
|
|
for (int i = 0; i < pathElementCount; i++) {
|
|
const auto &element = pathElements[i];
|
|
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 1) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)element.vertex.point.x;
|
|
pointsY[pointCount] = (float)element.vertex.point.y;
|
|
pointCount++;
|
|
|
|
if (i != 0) {
|
|
const auto &previousElement = pathElements[i - 1];
|
|
if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
|
} else {
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 2) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)previousElement.vertex.outTangent.x;
|
|
pointsY[pointCount] = (float)previousElement.vertex.outTangent.y;
|
|
pointCount++;
|
|
pointsX[pointCount] = (float)element.vertex.inTangent.x;
|
|
pointsY[pointCount] = (float)element.vertex.inTangent.y;
|
|
pointCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
context.pointsX = pointsX;
|
|
context.pointsY = pointsY;
|
|
context.pointsSize = pointsSize;
|
|
|
|
if (pointCount == 0) {
|
|
return CGRect(0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
return calculateBoundingRectOpt(pointsX, pointsY, pointCount);
|
|
}
|
|
|
|
CGRect bezierPathsBoundingBox(std::vector<BezierPath> const &paths) {
|
|
int pointCount = 0;
|
|
|
|
float *pointsX = (float *)malloc(128 * 4);
|
|
float *pointsY = (float *)malloc(128 * 4);
|
|
int pointsSize = 128;
|
|
|
|
for (const auto &path : paths) {
|
|
PathElement const *pathElements = path.elements().data();
|
|
int pathElementCount = (int)path.elements().size();
|
|
|
|
for (int i = 0; i < pathElementCount; i++) {
|
|
const auto &element = pathElements[i];
|
|
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 1) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)element.vertex.point.x;
|
|
pointsY[pointCount] = (float)element.vertex.point.y;
|
|
pointCount++;
|
|
|
|
if (i != 0) {
|
|
const auto &previousElement = pathElements[i - 1];
|
|
if (previousElement.vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) {
|
|
} else {
|
|
if (pointsSize < pointCount + 1) {
|
|
pointsSize = (pointCount + 2) * 2;
|
|
pointsX = (float *)realloc(pointsX, pointsSize * 4);
|
|
pointsY = (float *)realloc(pointsY, pointsSize * 4);
|
|
}
|
|
pointsX[pointCount] = (float)previousElement.vertex.outTangent.x;
|
|
pointsY[pointCount] = (float)previousElement.vertex.outTangent.y;
|
|
pointCount++;
|
|
pointsX[pointCount] = (float)element.vertex.inTangent.x;
|
|
pointsY[pointCount] = (float)element.vertex.inTangent.y;
|
|
pointCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(pointsX);
|
|
free(pointsY);
|
|
|
|
if (pointCount == 0) {
|
|
return CGRect(0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
return calculateBoundingRectOpt(pointsX, pointsY, pointCount);
|
|
}
|
|
|
|
PathContents::PathContents(BezierPathContents const &bezierPath) {
|
|
std::optional<PathElement> previousElement;
|
|
for (const auto &element : bezierPath.elements) {
|
|
if (previousElement.has_value()) {
|
|
_elements.emplace_back(element.vertex.point, previousElement->vertex.outTangent, element.vertex.inTangent);
|
|
} else {
|
|
_elements.emplace_back(element.vertex.point, Vector2D(0.0, 0.0), Vector2D(0.0, 0.0));
|
|
}
|
|
previousElement = element;
|
|
}
|
|
|
|
if (bezierPath.closed.value_or(true)) {
|
|
_isClosed = true;
|
|
} else {
|
|
_isClosed = false;
|
|
}
|
|
}
|
|
|
|
PathContents::~PathContents() {
|
|
}
|
|
|
|
std::shared_ptr<BezierPathContents> PathContents::bezierPath() const {
|
|
auto result = std::make_shared<BezierPathContents>();
|
|
|
|
bool isFirst = true;
|
|
for (const auto &element : _elements) {
|
|
if (isFirst) {
|
|
isFirst = false;
|
|
} else {
|
|
result->elements.push_back(PathElement(CurveVertex::absolute(
|
|
element.point,
|
|
element.cp1,
|
|
element.cp2
|
|
)));
|
|
}
|
|
}
|
|
|
|
result->closed = _isClosed;
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|