// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "impeller/geometry/path.h" #include #include "flutter/fml/logging.h" #include "impeller/geometry/path_component.h" #include "impeller/geometry/point.h" namespace impeller { Path::Path() : data_(new Data()) {} Path::Path(Data data) : data_(std::make_shared(std::move(data))) {} Path::~Path() = default; std::tuple Path::Polyline::GetContourPointBounds( size_t contour_index) const { if (contour_index >= contours.size()) { return {points->size(), points->size()}; } const size_t start_index = contours.at(contour_index).start_index; const size_t end_index = (contour_index >= contours.size() - 1) ? points->size() : contours.at(contour_index + 1).start_index; return std::make_tuple(start_index, end_index); } size_t Path::GetComponentCount(std::optional type) const { if (!type.has_value()) { return data_->components.size(); } auto type_value = type.value(); size_t count = 0u; for (const auto& component : data_->components) { if (component == type_value) { count++; } } return count; } FillType Path::GetFillType() const { return data_->fill; } bool Path::IsConvex() const { return data_->convexity == Convexity::kConvex; } bool Path::IsEmpty() const { return data_->points.empty() || (data_->components.size() == 1 && data_->components[0] == ComponentType::kContour); } void Path::WritePolyline(Scalar scale, VertexWriter& writer) const { auto& path_components = data_->components; auto& path_points = data_->points; bool started_contour = false; bool first_point = true; size_t storage_offset = 0u; for (size_t component_i = 0; component_i < path_components.size(); component_i++) { const auto& path_component = path_components[component_i]; switch (path_component) { case ComponentType::kLinear: { const LinearPathComponent* linear = reinterpret_cast( &path_points[storage_offset]); if (first_point) { writer.Write(linear->p1); first_point = false; } writer.Write(linear->p2); break; } case ComponentType::kQuadratic: { const QuadraticPathComponent* quad = reinterpret_cast( &path_points[storage_offset]); if (first_point) { writer.Write(quad->p1); first_point = false; } quad->ToLinearPathComponents(scale, writer); break; } case ComponentType::kCubic: { const CubicPathComponent* cubic = reinterpret_cast( &path_points[storage_offset]); if (first_point) { writer.Write(cubic->p1); first_point = false; } cubic->ToLinearPathComponents(scale, writer); break; } case Path::ComponentType::kContour: if (component_i == path_components.size() - 1) { // If the last component is a contour, that means it's an empty // contour, so skip it. continue; } // The contour component type is the first segment in a contour. // Since this should contain the destination (if closed), we // can start with this point. If there was already an open // contour, or we've reached the end of the verb list, we // also close the contour. if (started_contour) { writer.EndContour(); } started_contour = true; first_point = true; } storage_offset += VerbToOffset(path_component); } if (started_contour) { writer.EndContour(); } } bool Path::GetLinearComponentAtIndex(size_t index, LinearPathComponent& linear) const { auto& components = data_->components; if (index >= components.size() || components[index] != ComponentType::kLinear) { return false; } size_t storage_offset = 0u; for (auto i = 0u; i < index; i++) { storage_offset += VerbToOffset(components[i]); } auto& points = data_->points; linear = LinearPathComponent(points[storage_offset], points[storage_offset + 1]); return true; } bool Path::GetQuadraticComponentAtIndex( size_t index, QuadraticPathComponent& quadratic) const { auto& components = data_->components; if (index >= components.size() || components[index] != ComponentType::kQuadratic) { return false; } size_t storage_offset = 0u; for (auto i = 0u; i < index; i++) { storage_offset += VerbToOffset(components[i]); } auto& points = data_->points; quadratic = QuadraticPathComponent(points[storage_offset], points[storage_offset + 1], points[storage_offset + 2]); return true; } bool Path::GetCubicComponentAtIndex(size_t index, CubicPathComponent& cubic) const { auto& components = data_->components; if (index >= components.size() || components[index] != ComponentType::kCubic) { return false; } size_t storage_offset = 0u; for (auto i = 0u; i < index; i++) { storage_offset += VerbToOffset(components[i]); } auto& points = data_->points; cubic = CubicPathComponent(points[storage_offset], points[storage_offset + 1], points[storage_offset + 2], points[storage_offset + 3]); return true; } bool Path::GetContourComponentAtIndex(size_t index, ContourComponent& move) const { auto& components = data_->components; if (index >= components.size() || components[index] != ComponentType::kContour) { return false; } size_t storage_offset = 0u; for (auto i = 0u; i < index; i++) { storage_offset += VerbToOffset(components[i]); } auto& points = data_->points; move = ContourComponent(points[storage_offset], points[storage_offset + 1]); return true; } Path::Polyline::Polyline(Path::Polyline::PointBufferPtr point_buffer, Path::Polyline::ReclaimPointBufferCallback reclaim) : points(std::move(point_buffer)), reclaim_points_(std::move(reclaim)) { FML_DCHECK(points); } Path::Polyline::Polyline(Path::Polyline&& other) { points = std::move(other.points); reclaim_points_ = std::move(other.reclaim_points_); contours = std::move(other.contours); } Path::Polyline::~Polyline() { if (reclaim_points_) { points->clear(); reclaim_points_(std::move(points)); } } void Path::EndContour( size_t storage_offset, Polyline& polyline, size_t component_index, std::vector& poly_components) const { auto& path_components = data_->components; auto& path_points = data_->points; // Whenever a contour has ended, extract the exact end direction from // the last component. if (polyline.contours.empty() || component_index == 0) { return; } auto& contour = polyline.contours.back(); contour.end_direction = Vector2(0, 1); contour.components = poly_components; poly_components.clear(); size_t previous_index = component_index - 1; storage_offset -= VerbToOffset(path_components[previous_index]); while (previous_index >= 0 && storage_offset >= 0) { const auto& path_component = path_components[previous_index]; switch (path_component) { case ComponentType::kLinear: { auto* linear = reinterpret_cast( &path_points[storage_offset]); auto maybe_end = linear->GetEndDirection(); if (maybe_end.has_value()) { contour.end_direction = maybe_end.value(); return; } break; } case ComponentType::kQuadratic: { auto* quad = reinterpret_cast( &path_points[storage_offset]); auto maybe_end = quad->GetEndDirection(); if (maybe_end.has_value()) { contour.end_direction = maybe_end.value(); return; } break; } case ComponentType::kCubic: { auto* cubic = reinterpret_cast( &path_points[storage_offset]); auto maybe_end = cubic->GetEndDirection(); if (maybe_end.has_value()) { contour.end_direction = maybe_end.value(); return; } break; } case ComponentType::kContour: { // Hit previous contour, return. return; }; } storage_offset -= VerbToOffset(path_component); previous_index--; } }; Path::Polyline Path::CreatePolyline( Scalar scale, Path::Polyline::PointBufferPtr point_buffer, Path::Polyline::ReclaimPointBufferCallback reclaim) const { Polyline polyline(std::move(point_buffer), std::move(reclaim)); auto& path_components = data_->components; auto& path_points = data_->points; std::optional start_direction; std::vector poly_components; size_t storage_offset = 0u; size_t component_i = 0; for (; component_i < path_components.size(); component_i++) { auto path_component = path_components[component_i]; switch (path_component) { case ComponentType::kLinear: { poly_components.push_back({ .component_start_index = polyline.points->size() - 1, .is_curve = false, }); auto* linear = reinterpret_cast( &path_points[storage_offset]); linear->AppendPolylinePoints(*polyline.points); if (!start_direction.has_value()) { start_direction = linear->GetStartDirection(); } break; } case ComponentType::kQuadratic: { poly_components.push_back({ .component_start_index = polyline.points->size() - 1, .is_curve = true, }); auto* quad = reinterpret_cast( &path_points[storage_offset]); quad->AppendPolylinePoints(scale, *polyline.points); if (!start_direction.has_value()) { start_direction = quad->GetStartDirection(); } break; } case ComponentType::kCubic: { poly_components.push_back({ .component_start_index = polyline.points->size() - 1, .is_curve = true, }); auto* cubic = reinterpret_cast( &path_points[storage_offset]); cubic->AppendPolylinePoints(scale, *polyline.points); if (!start_direction.has_value()) { start_direction = cubic->GetStartDirection(); } break; } case ComponentType::kContour: if (component_i == path_components.size() - 1) { // If the last component is a contour, that means it's an empty // contour, so skip it. break; } if (!polyline.contours.empty()) { polyline.contours.back().start_direction = start_direction.value_or(Vector2(0, -1)); start_direction = std::nullopt; } EndContour(storage_offset, polyline, component_i, poly_components); auto* contour = reinterpret_cast( &path_points[storage_offset]); polyline.contours.push_back(PolylineContour{ .start_index = polyline.points->size(), // .is_closed = contour->IsClosed(), // .start_direction = Vector2(0, -1), // .components = poly_components // }); polyline.points->push_back(contour->destination); break; } storage_offset += VerbToOffset(path_component); } // Subtract the last storage offset increment so that the storage lookup is // correct, including potentially an empty contour as well. if (component_i > 0 && path_components.back() == ComponentType::kContour) { storage_offset -= VerbToOffset(ComponentType::kContour); component_i--; } if (!polyline.contours.empty()) { polyline.contours.back().start_direction = start_direction.value_or(Vector2(0, -1)); } EndContour(storage_offset, polyline, component_i, poly_components); return polyline; } std::optional Path::GetBoundingBox() const { return data_->bounds; } std::optional Path::GetTransformedBoundingBox( const Matrix& transform) const { auto bounds = GetBoundingBox(); if (!bounds.has_value()) { return std::nullopt; } return bounds->TransformBounds(transform); } } // namespace impeller