已合并
feat(utils): add selection geometry utils for rect/point operations #158
feat(utils): add selection geometry utils for rect/point operations #158
已合并
no86创建于 7月31日
2 个文件变更+1887-0
@@ -0,0 +1,186 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#ifndef SELECTION_GEOMETRY_UTILS_H
17+#define SELECTION_GEOMETRY_UTILS_H
18+ 
19+#include <cstddef>
20+#include <cstdint>
21+#include <utility>
22+#include <vector>
23+ 
24+namespace OHOS {
25+namespace SelectionFwk {
26+ 
27+struct SelectionPoint {
28+ int32_t x = 0;
29+ int32_t y = 0;
30+ 
31+ SelectionPoint() = default;
32+ SelectionPoint(int32_t px, int32_t py) : x(px), y(py) {}
33+};
34+ 
35+struct SelectionRect {
36+ int32_t x = 0;
37+ int32_t y = 0;
38+ int32_t width = 0;
39+ int32_t height = 0;
40+ 
41+ SelectionRect() = default;
42+ SelectionRect(int32_t px, int32_t py, int32_t pw, int32_t ph)
43+ : x(px), y(py), width(pw), height(ph) {}
44+ 
45+ bool IsEmpty() const;
46+ bool IsNormalized() const;
47+ int64_t Area() const;
48+ bool operator==(const SelectionRect &other) const;
49+ bool operator!=(const SelectionRect &other) const;
50+};
51+ 
52+class SelectionGeometryUtils {
53+public:
54+ static int32_t ClampInt(int32_t value, int32_t lo, int32_t hi);
55+ static int32_t ClampToInt32(int64_t value);
56+ static int32_t SnapToPixel(double value);
57+ static int32_t SafeAddInt32(int32_t a, int32_t b);
58+ static int32_t SafeSubInt32(int32_t a, int32_t b);
59+ static int64_t SafeMulInt64(int32_t a, int32_t b);
60+ 
61+ static SelectionRect MakeEmptyRect();
62+ static SelectionRect NormalizeRect(const SelectionRect &rect);
63+ static int64_t RightEdge(const SelectionRect &rect);
64+ static int64_t BottomEdge(const SelectionRect &rect);
65+ static int64_t CenterX(const SelectionRect &rect);
66+ static int64_t CenterY(const SelectionRect &rect);
67+ static int64_t Perimeter(const SelectionRect &rect);
68+ static double AspectRatio(const SelectionRect &rect);
69+ 
70+ static bool ContainsPoint(const SelectionRect &rect, const SelectionPoint &point);
71+ static bool ContainsPoint(const SelectionRect &rect, int32_t px, int32_t py);
72+ static bool ContainsRect(const SelectionRect &outer, const SelectionRect &inner);
73+ static bool Intersects(const SelectionRect &a, const SelectionRect &b);
74+ static bool EqualsTolerance(const SelectionRect &a, const SelectionRect &b, int32_t tol);
75+ 
76+ static SelectionRect IntersectRect(const SelectionRect &a, const SelectionRect &b);
77+ static SelectionRect UnionRect(const SelectionRect &a, const SelectionRect &b);
78+ static std::vector<SelectionRect> SubtractRect(const SelectionRect &a, const SelectionRect &b);
79+ static std::vector<SelectionRect> IntersectListWithRect(const std::vector<SelectionRect> &list,
80+ const SelectionRect &rect);
81+ static SelectionRect UnionListBounds(const std::vector<SelectionRect> &list);
82+ 
83+ static double DistancePointRect(const SelectionPoint &point, const SelectionRect &rect);
84+ static int32_t SignedDistanceXPointRect(const SelectionPoint &point, const SelectionRect &rect);
85+ static int32_t SignedDistanceYPointRect(const SelectionPoint &point, const SelectionRect &rect);
86+ static double DistanceRectRect(const SelectionRect &a, const SelectionRect &b);
87+ static int32_t HorizontalGap(const SelectionRect &a, const SelectionRect &b);
88+ static int32_t VerticalGap(const SelectionRect &a, const SelectionRect &b);
89+ 
90+ static bool IsAdjacent(const SelectionRect &a, const SelectionRect &b, int32_t tol);
91+ static bool IsHorizontallyAligned(const SelectionRect &a, const SelectionRect &b, int32_t tol);
92+ static bool IsVerticallyAligned(const SelectionRect &a, const SelectionRect &b, int32_t tol);
93+ static bool AreOnSameRow(const SelectionRect &a, const SelectionRect &b, int32_t tol);
94+ static bool AreOnSameColumn(const SelectionRect &a, const SelectionRect &b, int32_t tol);
95+ 
96+ static SelectionRect Inflate(const SelectionRect &rect, int32_t left, int32_t top,
97+ int32_t right, int32_t bottom);
98+ static SelectionRect InflateUniform(const SelectionRect &rect, int32_t amount);
99+ static SelectionRect Deflate(const SelectionRect &rect, int32_t left, int32_t top,
100+ int32_t right, int32_t bottom);
101+ static SelectionRect DeflateUniform(const SelectionRect &rect, int32_t amount);
102+ static SelectionRect Offset(const SelectionRect &rect, int32_t dx, int32_t dy);
103+ static SelectionRect Scale(const SelectionRect &rect, double sx, double sy);
104+ static SelectionRect Transpose(const SelectionRect &rect);
105+ static SelectionRect MirrorHorizontal(const SelectionRect &rect, int32_t axisX);
106+ static SelectionRect MirrorVertical(const SelectionRect &rect, int32_t axisY);
107+ static SelectionRect Rotate90(const SelectionRect &rect, const SelectionPoint &pivot);
108+ 
109+ static std::vector<SelectionRect> MergeOverlapping(const std::vector<SelectionRect> &list,
110+ int32_t tol);
111+ static int64_t TotalArea(const std::vector<SelectionRect> &list);
112+ static SelectionRect BoundsOfList(const std::vector<SelectionRect> &list);
113+ static bool ContainsAny(const std::vector<SelectionRect> &list, const SelectionRect &rect);
114+ static bool ContainsAll(const std::vector<SelectionRect> &list, const SelectionRect &rect);
115+ static std::vector<SelectionRect> RemoveContained(const std::vector<SelectionRect> &list);
116+ static std::vector<SelectionRect> Deduplicate(const std::vector<SelectionRect> &list, int32_t tol);
117+ 
118+ static std::vector<std::vector<SelectionRect>> GroupByRows(const std::vector<SelectionRect> &list,
119+ int32_t rowTol);
120+ static std::vector<SelectionRect> SortByRow(const std::vector<SelectionRect> &list);
121+ 
122+ static SelectionRect ClipToBounds(const SelectionRect &rect, const SelectionRect &bounds);
123+ static std::vector<SelectionRect> ClipRectListToBounds(const std::vector<SelectionRect> &list,
124+ const SelectionRect &bounds);
125+ 
126+ static bool ClipSegmentToRect(SelectionPoint &p1, SelectionPoint &p2, const SelectionRect &rect);
127+ static bool SegmentIntersectsRect(const SelectionPoint &p1, const SelectionPoint &p2,
128+ const SelectionRect &rect);
129+ 
130+ static std::vector<SelectionPoint> ClipPolygonToRect(const std::vector<SelectionPoint> &polygon,
131+ const SelectionRect &rect);
132+ 
133+ static int64_t UnionArea(const std::vector<SelectionRect> &list);
134+ 
135+ static SelectionRect MapFromParent(const SelectionRect &rect, const SelectionPoint &parentOrigin);
136+ static SelectionRect MapToParent(const SelectionRect &rect, const SelectionPoint &parentOrigin);
137+ static SelectionRect NormalizeForRTL(const SelectionRect &rect, int32_t parentWidth);
138+ 
139+ static SelectionRect MinRectByArea(const SelectionRect &a, const SelectionRect &b);
140+ static SelectionRect MaxRectByArea(const SelectionRect &a, const SelectionRect &b);
141+ 
142+ static double PointDistance(const SelectionPoint &a, const SelectionPoint &b);
143+ static bool PointEqualsTolerance(const SelectionPoint &a, const SelectionPoint &b, int32_t tol);
144+ static SelectionPoint PointLerp(const SelectionPoint &a, const SelectionPoint &b, double t);
145+ static SelectionPoint PointTranslate(const SelectionPoint &pt, int32_t dx, int32_t dy);
146+ static SelectionPoint PointRotate90(const SelectionPoint &pt, const SelectionPoint &pivot);
147+ static SelectionPoint PointMirrorHorizontal(const SelectionPoint &pt, int32_t axisX);
148+ static SelectionPoint PointMirrorVertical(const SelectionPoint &pt, int32_t axisY);
149+ static double SegmentLength(const SelectionPoint &a, const SelectionPoint &b);
150+ static double PointToSegmentDistance(const SelectionPoint &p, const SelectionPoint &s,
151+ const SelectionPoint &e);
152+ static SelectionPoint FootOfPerpendicular(const SelectionPoint &p, const SelectionPoint &s,
153+ const SelectionPoint &e);
154+ static bool IsPointOnSegment(const SelectionPoint &p, const SelectionPoint &s,
155+ const SelectionPoint &e);
156+ static std::vector<SelectionPoint> RectCorners(const SelectionRect &rect);
157+ static std::vector<SelectionPoint> RectEdgeMidpoints(const SelectionRect &rect);
158+ static int32_t ClosestEdgeIndex(const SelectionRect &rect, const SelectionPoint &point);
159+ static SelectionRect ExpandToContain(const SelectionRect &rect, const SelectionPoint &point);
160+ static SelectionRect ShrinkToAspectRatio(const SelectionRect &rect, double targetRatio);
161+ static std::vector<SelectionRect> SplitGrid(const SelectionRect &rect, int32_t rows, int32_t cols);
162+ static bool IsSquare(const SelectionRect &rect);
163+ static int32_t LargerAxis(const SelectionRect &rect);
164+ static int32_t SmallerAxis(const SelectionRect &rect);
165+ static SelectionRect CenterAt(const SelectionRect &rect, const SelectionPoint &center);
166+};
167+ 
168+class SelectionRectCombiner {
169+public:
170+ SelectionRectCombiner() = default;
171+ ~SelectionRectCombiner() = default;
172+ 
173+ void Add(const SelectionRect &rect);
174+ std::vector<SelectionRect> Result() const;
175+ void Clear();
176+ size_t Size() const;
177+ bool Empty() const;
178+ 
179+private:
180+ void TryMergeAdjacent();
181+ 
182+ std::vector<SelectionRect> rects_;
183+};
184+} // namespace SelectionFwk
185+} // namespace OHOS
186+#endif // SELECTION_GEOMETRY_UTILS_H
@@ -0,0 +1,1701 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#include "selection_geometry_utils.h"
17+ 
18+#include <algorithm>
19+#include <cstddef>
20+#include <cmath>
21+#include <cstdint>
22+#include <limits>
23+#include <numeric>
24+#include <vector>
25+ 
26+namespace OHOS {
27+namespace SelectionFwk {
28+ 
29+namespace {
30+constexpr int32_t INT32_MIN_VALUE = std::numeric_limits<int32_t>::min();
31+constexpr int32_t INT32_MAX_VALUE = std::numeric_limits<int32_t>::max();
32+ 
33+constexpr size_t MIN_POLYGON_VERTICES = 3;
34+constexpr int32_t RECT_EDGE_COUNT = 4;
35+constexpr int32_t Y_BOUNDS_PER_RECT = 2;
36+constexpr int64_t HALF_FACTOR = 2;
37+constexpr int64_t PERIMETER_FACTOR = 2;
38+constexpr int64_t MIRROR_FACTOR = 2;
39+constexpr double ROUND_HALF = 0.5;
40+constexpr double CLIP_T_MIN = 0.0;
41+constexpr double CLIP_T_MAX = 1.0;
42+ 
43+constexpr int EDGE_LEFT = 0;
44+constexpr int EDGE_RIGHT = 1;
45+constexpr int EDGE_TOP = 2;
46+constexpr int EDGE_BOTTOM = 3;
47+ 
48+int64_t AbsInt64(int64_t value)
49+{
50+ return value < 0 ? -value : value;
51+}
52+ 
53+struct Interval {
54+ int64_t start;
55+ int64_t end;
56+};
57+ 
58+bool OverlapOrAdjacent1D(int64_t a0, int64_t a1, int64_t b0, int64_t b1, int32_t tol)
59+{
60+ if (a0 > a1) {
61+ std::swap(a0, a1);
62+ }
63+ if (b0 > b1) {
64+ std::swap(b0, b1);
65+ }
66+ int64_t tol64 = static_cast<int64_t>(tol);
67+ if (tol64 < 0) {
68+ tol64 = 0;
69+ }
70+ return !(a1 + tol64 < b0 || b1 + tol64 < a0);
71+}
72+ 
73+bool Overlap1D(int64_t a0, int64_t a1, int64_t b0, int64_t b1)
74+{
75+ if (a0 > a1) {
76+ std::swap(a0, a1);
77+ }
78+ if (b0 > b1) {
79+ std::swap(b0, b1);
80+ }
81+ return !(a1 <= b0 || b1 <= a0);
82+}
83+ 
84+SelectionRect ClampRect(int64_t x, int64_t y, int64_t width, int64_t height)
85+{
86+ int64_t nx = x;
87+ int64_t ny = y;
88+ int64_t nw = width;
89+ int64_t nh = height;
90+ if (nw < 0) {
91+ nx += nw;
92+ nw = -nw;
93+ }
94+ if (nh < 0) {
95+ ny += nh;
96+ nh = -nh;
97+ }
98+ int64_t left = nx;
99+ int64_t top = ny;
100+ int64_t right = nx + nw;
101+ int64_t bottom = ny + nh;
102+ if (left < INT32_MIN_VALUE) {
103+ left = INT32_MIN_VALUE;
104+ }
105+ if (left > INT32_MAX_VALUE) {
106+ left = INT32_MAX_VALUE;
107+ }
108+ if (top < INT32_MIN_VALUE) {
109+ top = INT32_MIN_VALUE;
110+ }
111+ if (top > INT32_MAX_VALUE) {
112+ top = INT32_MAX_VALUE;
113+ }
114+ if (right < INT32_MIN_VALUE) {
115+ right = INT32_MIN_VALUE;
116+ }
117+ if (right > INT32_MAX_VALUE) {
118+ right = INT32_MAX_VALUE;
119+ }
120+ if (bottom < INT32_MIN_VALUE) {
121+ bottom = INT32_MIN_VALUE;
122+ }
123+ if (bottom > INT32_MAX_VALUE) {
124+ bottom = INT32_MAX_VALUE;
125+ }
126+ if (right < left) {
127+ right = left;
128+ }
129+ if (bottom < top) {
130+ bottom = top;
131+ }
132+ return SelectionRect(static_cast<int32_t>(left), static_cast<int32_t>(top),
133+ static_cast<int32_t>(right - left), static_cast<int32_t>(bottom - top));
134+}
135+ 
136+bool PolygonInsidePoint(const SelectionPoint &pt, int edge, const SelectionRect &bounds)
137+{
138+ int64_t x = pt.x;
139+ int64_t y = pt.y;
140+ int64_t l = bounds.x;
141+ int64_t r = SelectionGeometryUtils::RightEdge(bounds);
142+ int64_t t = bounds.y;
143+ int64_t b = SelectionGeometryUtils::BottomEdge(bounds);
144+ switch (edge) {
145+ case EDGE_LEFT:
146+ return x >= l;
147+ case EDGE_RIGHT:
148+ return x < r;
149+ case EDGE_TOP:
150+ return y >= t;
151+ case EDGE_BOTTOM:
152+ return y < b;
153+ default:
154+ return false;
155+ }
156+}
157+ 
158+SelectionPoint PolygonEdgeIntersect(const SelectionPoint &s, const SelectionPoint &e,
159+ int edge, const SelectionRect &bounds)
160+{
161+ int64_t x1 = s.x;
162+ int64_t y1 = s.y;
163+ int64_t x2 = e.x;
164+ int64_t y2 = e.y;
165+ int64_t dx = x2 - x1;
166+ int64_t dy = y2 - y1;
167+ int64_t l = bounds.x;
168+ int64_t r = SelectionGeometryUtils::RightEdge(bounds);
169+ int64_t t = bounds.y;
170+ int64_t b = SelectionGeometryUtils::BottomEdge(bounds);
171+ switch (edge) {
172+ case EDGE_LEFT: {
173+ if (dx == 0) {
174+ return SelectionPoint(static_cast<int32_t>(l), static_cast<int32_t>(y1));
175+ }
176+ double tt = static_cast<double>(l - x1) / static_cast<double>(dx);
177+ int64_t ny = y1 + static_cast<int64_t>(tt * static_cast<double>(dy));
178+ return SelectionPoint(static_cast<int32_t>(l), static_cast<int32_t>(ny));
179+ }
180+ case EDGE_RIGHT: {
181+ if (dx == 0) {
182+ return SelectionPoint(static_cast<int32_t>(r), static_cast<int32_t>(y1));
183+ }
184+ double tt = static_cast<double>(r - x1) / static_cast<double>(dx);
185+ int64_t ny = y1 + static_cast<int64_t>(tt * static_cast<double>(dy));
186+ return SelectionPoint(static_cast<int32_t>(r), static_cast<int32_t>(ny));
187+ }
188+ case EDGE_TOP: {
189+ if (dy == 0) {
190+ return SelectionPoint(static_cast<int32_t>(x1), static_cast<int32_t>(t));
191+ }
192+ double tt = static_cast<double>(t - y1) / static_cast<double>(dy);
193+ int64_t nx = x1 + static_cast<int64_t>(tt * static_cast<double>(dx));
194+ return SelectionPoint(static_cast<int32_t>(nx), static_cast<int32_t>(t));
195+ }
196+ case EDGE_BOTTOM: {
197+ if (dy == 0) {
198+ return SelectionPoint(static_cast<int32_t>(x1), static_cast<int32_t>(b));
199+ }
200+ double tt = static_cast<double>(b - y1) / static_cast<double>(dy);
201+ int64_t nx = x1 + static_cast<int64_t>(tt * static_cast<double>(dx));
202+ return SelectionPoint(static_cast<int32_t>(nx), static_cast<int32_t>(b));
203+ }
204+ default:
205+ return e;
206+ }
207+}
208+ 
209+std::vector<SelectionPoint> ClipPolygonSingleEdge(const std::vector<SelectionPoint> &input,
210+ int edge, const SelectionRect &bounds)
211+{
212+ std::vector<SelectionPoint> output;
213+ size_t n = input.size();
214+ if (n == 0) {
215+ return output;
216+ }
217+ for (size_t i = 0; i < n; ++i) {
218+ SelectionPoint cur = input[i];
219+ SelectionPoint prev = input[(i + n - 1) % n];
220+ bool curIn = PolygonInsidePoint(cur, edge, bounds);
221+ bool prevIn = PolygonInsidePoint(prev, edge, bounds);
222+ if (curIn) {
223+ if (!prevIn) {
224+ output.push_back(PolygonEdgeIntersect(prev, cur, edge, bounds));
225+ }
226+ output.push_back(cur);
227+ } else if (prevIn) {
228+ output.push_back(PolygonEdgeIntersect(prev, cur, edge, bounds));
229+ }
230+ }
231+ return output;
232+}
233+ 
234+bool ApplySegmentClipEdge(double p, double q, double &t0, double &t1)
235+{
236+ if (p == 0.0) {
237+ return q >= 0.0;
238+ }
239+ double t = q / p;
240+ if (p < 0.0) {
241+ if (t > t1) {
242+ return false;
243+ }
244+ if (t > t0) {
245+ t0 = t;
246+ }
247+ } else {
248+ if (t < t0) {
249+ return false;
250+ }
251+ if (t < t1) {
252+ t1 = t;
253+ }
254+ }
255+ return true;
256+}
257+ 
258+int64_t MergeIntervalLengths(std::vector<Interval> &intervals)
259+{
260+ if (intervals.empty()) {
261+ return 0;
262+ }
263+ std::sort(intervals.begin(), intervals.end(),
264+ [](const Interval &a, const Interval &b) { return a.start < b.start; });
265+ int64_t mergedLen = 0;
266+ int64_t curStart = intervals[0].start;
267+ int64_t curEnd = intervals[0].end;
268+ for (size_t k = 1; k < intervals.size(); ++k) {
269+ if (intervals[k].start <= curEnd) {
270+ if (intervals[k].end > curEnd) {
271+ curEnd = intervals[k].end;
272+ }
273+ } else {
274+ mergedLen += curEnd - curStart;
275+ curStart = intervals[k].start;
276+ curEnd = intervals[k].end;
277+ }
278+ }
279+ mergedLen += curEnd - curStart;
280+ return mergedLen;
281+}
282+ 
283+bool CanMergeRects(const SelectionRect &ri, const SelectionRect &rj, int32_t tol)
284+{
285+ int32_t hx = SelectionGeometryUtils::HorizontalGap(ri, rj);
286+ int32_t vy = SelectionGeometryUtils::VerticalGap(ri, rj);
287+ bool xOverlap = (hx <= 0);
288+ bool yOverlap = (vy <= 0);
289+ bool xAdj = (hx >= 0 && hx <= tol);
290+ bool yAdj = (vy >= 0 && vy <= tol);
291+ return (xOverlap && (yOverlap || yAdj)) || (yOverlap && (xOverlap || xAdj));
292+}
293+ 
294+size_t FindMergePartner(const std::vector<SelectionRect> &rects, size_t i, int32_t tol)
295+{
296+ for (size_t j = i + 1; j < rects.size(); ++j) {
297+ if (CanMergeRects(rects[i], rects[j], tol)) {
298+ return j;
299+ }
300+ }
301+ return rects.size();
302+}
303+ 
304+bool IsContainedByAny(size_t idx, const std::vector<SelectionRect> &list)
305+{
306+ for (size_t j = 0; j < list.size(); ++j) {
307+ if (idx == j) {
308+ continue;
309+ }
310+ if (list[idx] == list[j]) {
311+ if (j < idx) {
312+ return true;
313+ }
314+ continue;
315+ }
316+ if (SelectionGeometryUtils::ContainsRect(list[j], list[idx])) {
317+ return true;
318+ }
319+ }
320+ return false;
321+}
322+ 
323+size_t FindRowGroup(const SelectionRect &rect, const std::vector<std::vector<SelectionRect>> &groups,
324+ int32_t rowTol)
325+{
326+ for (size_t g = 0; g < groups.size(); ++g) {
327+ if (groups[g].empty()) {
328+ continue;
329+ }
330+ SelectionRect bounds = SelectionGeometryUtils::UnionListBounds(groups[g]);
331+ if (OverlapOrAdjacent1D(bounds.y, SelectionGeometryUtils::BottomEdge(bounds), rect.y,
332+ SelectionGeometryUtils::BottomEdge(rect), rowTol)) {
333+ return g;
334+ }
335+ }
336+ return groups.size();
337+}
338+} // namespace
339+ 
340+bool SelectionRect::IsEmpty() const
341+{
342+ return width <= 0 || height <= 0;
343+}
344+ 
345+bool SelectionRect::IsNormalized() const
346+{
347+ return width >= 0 && height >= 0;
348+}
349+ 
350+int64_t SelectionRect::Area() const
351+{
352+ int64_t w = AbsInt64(static_cast<int64_t>(width));
353+ int64_t h = AbsInt64(static_cast<int64_t>(height));
354+ return w * h;
355+}
356+ 
357+bool SelectionRect::operator==(const SelectionRect &other) const
358+{
359+ return x == other.x && y == other.y && width == other.width && height == other.height;
360+}
361+ 
362+bool SelectionRect::operator!=(const SelectionRect &other) const
363+{
364+ return !(*this == other);
365+}
366+ 
367+int32_t SelectionGeometryUtils::ClampInt(int32_t value, int32_t lo, int32_t hi)
368+{
369+ if (lo > hi) {
370+ std::swap(lo, hi);
371+ }
372+ if (value < lo) {
373+ return lo;
374+ }
375+ if (value > hi) {
376+ return hi;
377+ }
378+ return value;
379+}
380+ 
381+int32_t SelectionGeometryUtils::ClampToInt32(int64_t value)
382+{
383+ if (value < INT32_MIN_VALUE) {
384+ return INT32_MIN_VALUE;
385+ }
386+ if (value > INT32_MAX_VALUE) {
387+ return INT32_MAX_VALUE;
388+ }
389+ return static_cast<int32_t>(value);
390+}
391+ 
392+int32_t SelectionGeometryUtils::SnapToPixel(double value)
393+{
394+ if (value <= static_cast<double>(INT32_MIN_VALUE)) {
395+ return INT32_MIN_VALUE;
396+ }
397+ if (value >= static_cast<double>(INT32_MAX_VALUE)) {
398+ return INT32_MAX_VALUE;
399+ }
400+ if (value >= 0.0) {
401+ return static_cast<int32_t>(std::floor(value + ROUND_HALF));
402+ }
403+ return static_cast<int32_t>(std::ceil(value - ROUND_HALF));
404+}
405+ 
406+int32_t SelectionGeometryUtils::SafeAddInt32(int32_t a, int32_t b)
407+{
408+ int64_t sum = static_cast<int64_t>(a) + static_cast<int64_t>(b);
409+ return ClampToInt32(sum);
410+}
411+ 
412+int32_t SelectionGeometryUtils::SafeSubInt32(int32_t a, int32_t b)
413+{
414+ int64_t diff = static_cast<int64_t>(a) - static_cast<int64_t>(b);
415+ return ClampToInt32(diff);
416+}
417+ 
418+int64_t SelectionGeometryUtils::SafeMulInt64(int32_t a, int32_t b)
419+{
420+ return static_cast<int64_t>(a) * static_cast<int64_t>(b);
421+}
422+ 
423+SelectionRect SelectionGeometryUtils::MakeEmptyRect()
424+{
425+ return SelectionRect(0, 0, 0, 0);
426+}
427+ 
428+SelectionRect SelectionGeometryUtils::NormalizeRect(const SelectionRect &rect)
429+{
430+ return ClampRect(static_cast<int64_t>(rect.x), static_cast<int64_t>(rect.y),
431+ static_cast<int64_t>(rect.width), static_cast<int64_t>(rect.height));
432+}
433+ 
434+int64_t SelectionGeometryUtils::RightEdge(const SelectionRect &rect)
435+{
436+ return static_cast<int64_t>(rect.x) + static_cast<int64_t>(rect.width);
437+}
438+ 
439+int64_t SelectionGeometryUtils::BottomEdge(const SelectionRect &rect)
440+{
441+ return static_cast<int64_t>(rect.y) + static_cast<int64_t>(rect.height);
442+}
443+ 
444+int64_t SelectionGeometryUtils::CenterX(const SelectionRect &rect)
445+{
446+ int64_t w = AbsInt64(static_cast<int64_t>(rect.width));
447+ return static_cast<int64_t>(rect.x) + w / HALF_FACTOR;
448+}
449+ 
450+int64_t SelectionGeometryUtils::CenterY(const SelectionRect &rect)
451+{
452+ int64_t h = AbsInt64(static_cast<int64_t>(rect.height));
453+ return static_cast<int64_t>(rect.y) + h / HALF_FACTOR;
454+}
455+ 
456+int64_t SelectionGeometryUtils::Perimeter(const SelectionRect &rect)
457+{
458+ int64_t w = AbsInt64(static_cast<int64_t>(rect.width));
459+ int64_t h = AbsInt64(static_cast<int64_t>(rect.height));
460+ return PERIMETER_FACTOR * (w + h);
461+}
462+ 
463+double SelectionGeometryUtils::AspectRatio(const SelectionRect &rect)
464+{
465+ int64_t w = AbsInt64(static_cast<int64_t>(rect.width));
466+ int64_t h = AbsInt64(static_cast<int64_t>(rect.height));
467+ if (h == 0) {
468+ return 0.0;
469+ }
470+ return static_cast<double>(w) / static_cast<double>(h);
471+}
472+ 
473+bool SelectionGeometryUtils::ContainsPoint(const SelectionRect &rect, const SelectionPoint &point)
474+{
475+ return ContainsPoint(rect, point.x, point.y);
476+}
477+ 
478+bool SelectionGeometryUtils::ContainsPoint(const SelectionRect &rect, int32_t px, int32_t py)
479+{
480+ if (rect.IsEmpty()) {
481+ return false;
482+ }
483+ int64_t x = static_cast<int64_t>(px);
484+ int64_t y = static_cast<int64_t>(py);
485+ int64_t rx = static_cast<int64_t>(rect.x);
486+ int64_t ry = static_cast<int64_t>(rect.y);
487+ int64_t r = RightEdge(rect);
488+ int64_t b = BottomEdge(rect);
489+ if (r < rx) {
490+ std::swap(rx, r);
491+ }
492+ if (b < ry) {
493+ std::swap(ry, b);
494+ }
495+ return x >= rx && x < r && y >= ry && y < b;
496+}
497+ 
498+bool SelectionGeometryUtils::ContainsRect(const SelectionRect &outer, const SelectionRect &inner)
499+{
500+ if (outer.IsEmpty() || inner.IsEmpty()) {
501+ return false;
502+ }
503+ SelectionRect normOuter = NormalizeRect(outer);
504+ SelectionRect normInner = NormalizeRect(inner);
505+ int64_t orx = normOuter.x;
506+ int64_t ory = normOuter.y;
507+ int64_t orr = RightEdge(normOuter);
508+ int64_t orb = BottomEdge(normOuter);
509+ int64_t irx = normInner.x;
510+ int64_t iry = normInner.y;
511+ int64_t irr = RightEdge(normInner);
512+ int64_t irb = BottomEdge(normInner);
513+ return irx >= orx && iry >= ory && irr <= orr && irb <= orb;
514+}
515+ 
516+bool SelectionGeometryUtils::Intersects(const SelectionRect &a, const SelectionRect &b)
517+{
518+ if (a.IsEmpty() || b.IsEmpty()) {
519+ return false;
520+ }
521+ SelectionRect na = NormalizeRect(a);
522+ SelectionRect nb = NormalizeRect(b);
523+ int64_t al = na.x;
524+ int64_t at = na.y;
525+ int64_t ar = RightEdge(na);
526+ int64_t ab = BottomEdge(na);
527+ int64_t bl = nb.x;
528+ int64_t bt = nb.y;
529+ int64_t br = RightEdge(nb);
530+ int64_t bb = BottomEdge(nb);
531+ return al < br && bl < ar && at < bb && bt < ab;
532+}
533+ 
534+bool SelectionGeometryUtils::EqualsTolerance(const SelectionRect &a, const SelectionRect &b, int32_t tol)
535+{
536+ int32_t absTol = tol < 0 ? -tol : tol;
537+ int32_t dx = SafeSubInt32(a.x, b.x);
538+ if (dx < 0) {
539+ dx = -dx;
540+ }
541+ int32_t dy = SafeSubInt32(a.y, b.y);
542+ if (dy < 0) {
543+ dy = -dy;
544+ }
545+ int32_t dw = SafeSubInt32(a.width, b.width);
546+ if (dw < 0) {
547+ dw = -dw;
548+ }
549+ int32_t dh = SafeSubInt32(a.height, b.height);
550+ if (dh < 0) {
551+ dh = -dh;
552+ }
553+ return dx <= absTol && dy <= absTol && dw <= absTol && dh <= absTol;
554+}
555+ 
556+SelectionRect SelectionGeometryUtils::IntersectRect(const SelectionRect &a, const SelectionRect &b)
557+{
558+ if (a.IsEmpty() || b.IsEmpty()) {
559+ return MakeEmptyRect();
560+ }
561+ SelectionRect na = NormalizeRect(a);
562+ SelectionRect nb = NormalizeRect(b);
563+ int64_t l = std::max(static_cast<int64_t>(na.x), static_cast<int64_t>(nb.x));
564+ int64_t t = std::max(static_cast<int64_t>(na.y), static_cast<int64_t>(nb.y));
565+ int64_t r = std::min(RightEdge(na), RightEdge(nb));
566+ int64_t btm = std::min(BottomEdge(na), BottomEdge(nb));
567+ if (r <= l || btm <= t) {
568+ return MakeEmptyRect();
569+ }
570+ return ClampRect(l, t, r - l, btm - t);
571+}
572+ 
573+SelectionRect SelectionGeometryUtils::UnionRect(const SelectionRect &a, const SelectionRect &b)
574+{
575+ if (a.IsEmpty()) {
576+ return NormalizeRect(b);
577+ }
578+ if (b.IsEmpty()) {
579+ return NormalizeRect(a);
580+ }
581+ SelectionRect na = NormalizeRect(a);
582+ SelectionRect nb = NormalizeRect(b);
583+ int64_t l = std::min(static_cast<int64_t>(na.x), static_cast<int64_t>(nb.x));
584+ int64_t t = std::min(static_cast<int64_t>(na.y), static_cast<int64_t>(nb.y));
585+ int64_t r = std::max(RightEdge(na), RightEdge(nb));
586+ int64_t btm = std::max(BottomEdge(na), BottomEdge(nb));
587+ return ClampRect(l, t, r - l, btm - t);
588+}
589+ 
590+std::vector<SelectionRect> SelectionGeometryUtils::SubtractRect(const SelectionRect &a, const SelectionRect &b)
591+{
592+ std::vector<SelectionRect> result;
593+ if (a.IsEmpty()) {
594+ return result;
595+ }
596+ SelectionRect na = NormalizeRect(a);
597+ if (b.IsEmpty()) {
598+ result.push_back(na);
599+ return result;
600+ }
601+ SelectionRect inter = IntersectRect(na, b);
602+ if (inter.IsEmpty()) {
603+ result.push_back(na);
604+ return result;
605+ }
606+ int64_t al = na.x;
607+ int64_t at = na.y;
608+ int64_t ar = RightEdge(na);
609+ int64_t ab = BottomEdge(na);
610+ int64_t il = inter.x;
611+ int64_t it = inter.y;
612+ int64_t ir = RightEdge(inter);
613+ int64_t ib = BottomEdge(inter);
614+ if (il > al) {
615+ result.push_back(ClampRect(al, at, il - al, ab - at));
616+ }
617+ if (ir < ar) {
618+ result.push_back(ClampRect(ir, at, ar - ir, ab - at));
619+ }
620+ if (it > at) {
621+ result.push_back(ClampRect(il, at, ir - il, it - at));
622+ }
623+ if (ib < ab) {
624+ result.push_back(ClampRect(il, ib, ir - il, ab - ib));
625+ }
626+ return result;
627+}
628+ 
629+std::vector<SelectionRect> SelectionGeometryUtils::IntersectListWithRect(const std::vector<SelectionRect> &list,
630+ const SelectionRect &rect)
631+{
632+ std::vector<SelectionRect> result;
633+ for (const auto &item : list) {
634+ SelectionRect inter = IntersectRect(item, rect);
635+ if (!inter.IsEmpty()) {
636+ result.push_back(inter);
637+ }
638+ }
639+ return result;
640+}
641+ 
642+SelectionRect SelectionGeometryUtils::UnionListBounds(const std::vector<SelectionRect> &list)
643+{
644+ if (list.empty()) {
645+ return MakeEmptyRect();
646+ }
647+ SelectionRect acc = NormalizeRect(list.front());
648+ for (size_t i = 1; i < list.size(); ++i) {
649+ acc = UnionRect(acc, list[i]);
650+ }
651+ return acc;
652+}
653+ 
654+double SelectionGeometryUtils::DistancePointRect(const SelectionPoint &point, const SelectionRect &rect)
655+{
656+ if (rect.IsEmpty()) {
657+ return 0.0;
658+ }
659+ SelectionRect nr = NormalizeRect(rect);
660+ int64_t px = point.x;
661+ int64_t py = point.y;
662+ int64_t l = nr.x;
663+ int64_t t = nr.y;
664+ int64_t r = RightEdge(nr);
665+ int64_t b = BottomEdge(nr);
666+ int64_t dx = 0;
667+ int64_t dy = 0;
668+ if (px < l) {
669+ dx = l - px;
670+ } else if (px >= r) {
671+ dx = px - r + 1;
672+ }
673+ if (py < t) {
674+ dy = t - py;
675+ } else if (py >= b) {
676+ dy = py - b + 1;
677+ }
678+ double ddx = static_cast<double>(dx);
679+ double ddy = static_cast<double>(dy);
680+ return std::sqrt(ddx * ddx + ddy * ddy);
681+}
682+ 
683+int32_t SelectionGeometryUtils::SignedDistanceXPointRect(const SelectionPoint &point, const SelectionRect &rect)
684+{
685+ if (rect.IsEmpty()) {
686+ return 0;
687+ }
688+ SelectionRect nr = NormalizeRect(rect);
689+ int64_t px = point.x;
690+ int64_t l = nr.x;
691+ int64_t r = RightEdge(nr);
692+ if (px < l) {
693+ return ClampToInt32(px - l);
694+ }
695+ if (px >= r) {
696+ return ClampToInt32(px - r + 1);
697+ }
698+ return 0;
699+}
700+ 
701+int32_t SelectionGeometryUtils::SignedDistanceYPointRect(const SelectionPoint &point, const SelectionRect &rect)
702+{
703+ if (rect.IsEmpty()) {
704+ return 0;
705+ }
706+ SelectionRect nr = NormalizeRect(rect);
707+ int64_t py = point.y;
708+ int64_t t = nr.y;
709+ int64_t b = BottomEdge(nr);
710+ if (py < t) {
711+ return ClampToInt32(py - t);
712+ }
713+ if (py >= b) {
714+ return ClampToInt32(py - b + 1);
715+ }
716+ return 0;
717+}
718+ 
719+double SelectionGeometryUtils::DistanceRectRect(const SelectionRect &a, const SelectionRect &b)
720+{
721+ if (a.IsEmpty() || b.IsEmpty()) {
722+ return 0.0;
723+ }
724+ int32_t hx = HorizontalGap(a, b);
725+ int32_t vy = VerticalGap(a, b);
726+ if (hx <= 0 && vy <= 0) {
727+ return 0.0;
728+ }
729+ double dx = static_cast<double>(hx > 0 ? hx : 0);
730+ double dy = static_cast<double>(vy > 0 ? vy : 0);
731+ return std::sqrt(dx * dx + dy * dy);
732+}
733+ 
734+int32_t SelectionGeometryUtils::HorizontalGap(const SelectionRect &a, const SelectionRect &b)
735+{
736+ if (a.IsEmpty() || b.IsEmpty()) {
737+ return 0;
738+ }
739+ SelectionRect na = NormalizeRect(a);
740+ SelectionRect nb = NormalizeRect(b);
741+ int64_t al = na.x;
742+ int64_t ar = RightEdge(na);
743+ int64_t bl = nb.x;
744+ int64_t br = RightEdge(nb);
745+ if (ar <= bl) {
746+ return ClampToInt32(bl - ar);
747+ }
748+ if (br <= al) {
749+ return ClampToInt32(al - br);
750+ }
751+ return 0;
752+}
753+ 
754+int32_t SelectionGeometryUtils::VerticalGap(const SelectionRect &a, const SelectionRect &b)
755+{
756+ if (a.IsEmpty() || b.IsEmpty()) {
757+ return 0;
758+ }
759+ SelectionRect na = NormalizeRect(a);
760+ SelectionRect nb = NormalizeRect(b);
761+ int64_t at = na.y;
762+ int64_t ab = BottomEdge(na);
763+ int64_t bt = nb.y;
764+ int64_t bb = BottomEdge(nb);
765+ if (ab <= bt) {
766+ return ClampToInt32(bt - ab);
767+ }
768+ if (bb <= at) {
769+ return ClampToInt32(at - bb);
770+ }
771+ return 0;
772+}
773+ 
774+bool SelectionGeometryUtils::IsAdjacent(const SelectionRect &a, const SelectionRect &b, int32_t tol)
775+{
776+ if (a.IsEmpty() || b.IsEmpty()) {
777+ return false;
778+ }
779+ SelectionRect na = NormalizeRect(a);
780+ SelectionRect nb = NormalizeRect(b);
781+ int32_t hx = HorizontalGap(na, nb);
782+ int32_t vy = VerticalGap(na, nb);
783+ int32_t absTol = tol < 0 ? -tol : tol;
784+ bool xAdj = (hx >= 0 && hx <= absTol) && Overlap1D(na.y, BottomEdge(na), nb.y, BottomEdge(nb));
785+ bool yAdj = (vy >= 0 && vy <= absTol) && Overlap1D(na.x, RightEdge(na), nb.x, RightEdge(nb));
786+ return xAdj || yAdj;
787+}
788+ 
789+bool SelectionGeometryUtils::IsHorizontallyAligned(const SelectionRect &a, const SelectionRect &b, int32_t tol)
790+{
791+ if (a.IsEmpty() || b.IsEmpty()) {
792+ return false;
793+ }
794+ SelectionRect na = NormalizeRect(a);
795+ SelectionRect nb = NormalizeRect(b);
796+ int64_t at = na.y;
797+ int64_t ab = BottomEdge(na);
798+ int64_t bt = nb.y;
799+ int64_t bb = BottomEdge(nb);
800+ int64_t ac = (at + ab) / HALF_FACTOR;
801+ int64_t bc = (bt + bb) / HALF_FACTOR;
802+ int64_t diff = ac - bc;
803+ if (diff < 0) {
804+ diff = -diff;
805+ }
806+ int64_t absTol = tol < 0 ? -static_cast<int64_t>(tol) : static_cast<int64_t>(tol);
807+ return diff <= absTol;
808+}
809+ 
810+bool SelectionGeometryUtils::IsVerticallyAligned(const SelectionRect &a, const SelectionRect &b, int32_t tol)
811+{
812+ if (a.IsEmpty() || b.IsEmpty()) {
813+ return false;
814+ }
815+ SelectionRect na = NormalizeRect(a);
816+ SelectionRect nb = NormalizeRect(b);
817+ int64_t al = na.x;
818+ int64_t ar = RightEdge(na);
819+ int64_t bl = nb.x;
820+ int64_t br = RightEdge(nb);
821+ int64_t ac = (al + ar) / HALF_FACTOR;
822+ int64_t bc = (bl + br) / HALF_FACTOR;
823+ int64_t diff = ac - bc;
824+ if (diff < 0) {
825+ diff = -diff;
826+ }
827+ int64_t absTol = tol < 0 ? -static_cast<int64_t>(tol) : static_cast<int64_t>(tol);
828+ return diff <= absTol;
829+}
830+ 
831+bool SelectionGeometryUtils::AreOnSameRow(const SelectionRect &a, const SelectionRect &b, int32_t tol)
832+{
833+ if (a.IsEmpty() || b.IsEmpty()) {
834+ return false;
835+ }
836+ SelectionRect na = NormalizeRect(a);
837+ SelectionRect nb = NormalizeRect(b);
838+ return OverlapOrAdjacent1D(na.y, BottomEdge(na), nb.y, BottomEdge(nb), tol);
839+}
840+ 
841+bool SelectionGeometryUtils::AreOnSameColumn(const SelectionRect &a, const SelectionRect &b, int32_t tol)
842+{
843+ if (a.IsEmpty() || b.IsEmpty()) {
844+ return false;
845+ }
846+ SelectionRect na = NormalizeRect(a);
847+ SelectionRect nb = NormalizeRect(b);
848+ return OverlapOrAdjacent1D(na.x, RightEdge(na), nb.x, RightEdge(nb), tol);
849+}
850+ 
851+SelectionRect SelectionGeometryUtils::Inflate(const SelectionRect &rect, int32_t left, int32_t top,
852+ int32_t right, int32_t bottom)
853+{
854+ SelectionRect nr = NormalizeRect(rect);
855+ if (nr.IsEmpty()) {
856+ return MakeEmptyRect();
857+ }
858+ int64_t x = static_cast<int64_t>(nr.x) - static_cast<int64_t>(left);
859+ int64_t y = static_cast<int64_t>(nr.y) - static_cast<int64_t>(top);
860+ int64_t w = static_cast<int64_t>(nr.width) + static_cast<int64_t>(left) + static_cast<int64_t>(right);
861+ int64_t h = static_cast<int64_t>(nr.height) + static_cast<int64_t>(top) + static_cast<int64_t>(bottom);
862+ return ClampRect(x, y, w, h);
863+}
864+ 
865+SelectionRect SelectionGeometryUtils::InflateUniform(const SelectionRect &rect, int32_t amount)
866+{
867+ return Inflate(rect, amount, amount, amount, amount);
868+}
869+ 
870+SelectionRect SelectionGeometryUtils::Deflate(const SelectionRect &rect, int32_t left, int32_t top,
871+ int32_t right, int32_t bottom)
872+{
873+ SelectionRect nr = NormalizeRect(rect);
874+ if (nr.IsEmpty()) {
875+ return MakeEmptyRect();
876+ }
877+ int64_t x = static_cast<int64_t>(nr.x) + static_cast<int64_t>(left);
878+ int64_t y = static_cast<int64_t>(nr.y) + static_cast<int64_t>(top);
879+ int64_t w = static_cast<int64_t>(nr.width) - static_cast<int64_t>(left) - static_cast<int64_t>(right);
880+ int64_t h = static_cast<int64_t>(nr.height) - static_cast<int64_t>(top) - static_cast<int64_t>(bottom);
881+ return ClampRect(x, y, w, h);
882+}
883+ 
884+SelectionRect SelectionGeometryUtils::DeflateUniform(const SelectionRect &rect, int32_t amount)
885+{
886+ return Deflate(rect, amount, amount, amount, amount);
887+}
888+ 
889+SelectionRect SelectionGeometryUtils::Offset(const SelectionRect &rect, int32_t dx, int32_t dy)
890+{
891+ SelectionRect nr = NormalizeRect(rect);
892+ if (nr.IsEmpty()) {
893+ return MakeEmptyRect();
894+ }
895+ int64_t x = static_cast<int64_t>(nr.x) + static_cast<int64_t>(dx);
896+ int64_t y = static_cast<int64_t>(nr.y) + static_cast<int64_t>(dy);
897+ return ClampRect(x, y, nr.width, nr.height);
898+}
899+ 
900+SelectionRect SelectionGeometryUtils::Scale(const SelectionRect &rect, double sx, double sy)
901+{
902+ SelectionRect nr = NormalizeRect(rect);
903+ if (nr.IsEmpty()) {
904+ return MakeEmptyRect();
905+ }
906+ double x = static_cast<double>(nr.x) * sx;
907+ double y = static_cast<double>(nr.y) * sy;
908+ double w = static_cast<double>(nr.width) * sx;
909+ double h = static_cast<double>(nr.height) * sy;
910+ return ClampRect(static_cast<int64_t>(std::floor(x)), static_cast<int64_t>(std::floor(y)),
911+ static_cast<int64_t>(std::floor(w)), static_cast<int64_t>(std::floor(h)));
912+}
913+ 
914+SelectionRect SelectionGeometryUtils::Transpose(const SelectionRect &rect)
915+{
916+ SelectionRect nr = NormalizeRect(rect);
917+ if (nr.IsEmpty()) {
918+ return MakeEmptyRect();
919+ }
920+ return SelectionRect(nr.x, nr.y, nr.height, nr.width);
921+}
922+ 
923+SelectionRect SelectionGeometryUtils::MirrorHorizontal(const SelectionRect &rect, int32_t axisX)
924+{
925+ SelectionRect nr = NormalizeRect(rect);
926+ if (nr.IsEmpty()) {
927+ return MakeEmptyRect();
928+ }
929+ int64_t axis = static_cast<int64_t>(axisX);
930+ int64_t l = static_cast<int64_t>(nr.x);
931+ int64_t r = RightEdge(nr);
932+ int64_t newR = MIRROR_FACTOR * axis - l;
933+ int64_t newL = MIRROR_FACTOR * axis - r;
934+ if (newL > newR) {
935+ std::swap(newL, newR);
936+ }
937+ return ClampRect(newL, nr.y, newR - newL, nr.height);
938+}
939+ 
940+SelectionRect SelectionGeometryUtils::MirrorVertical(const SelectionRect &rect, int32_t axisY)
941+{
942+ SelectionRect nr = NormalizeRect(rect);
943+ if (nr.IsEmpty()) {
944+ return MakeEmptyRect();
945+ }
946+ int64_t axis = static_cast<int64_t>(axisY);
947+ int64_t t = static_cast<int64_t>(nr.y);
948+ int64_t b = BottomEdge(nr);
949+ int64_t newB = MIRROR_FACTOR * axis - t;
950+ int64_t newT = MIRROR_FACTOR * axis - b;
951+ if (newT > newB) {
952+ std::swap(newT, newB);
953+ }
954+ return ClampRect(nr.x, newT, nr.width, newB - newT);
955+}
956+ 
957+SelectionRect SelectionGeometryUtils::Rotate90(const SelectionRect &rect, const SelectionPoint &pivot)
958+{
959+ SelectionRect nr = NormalizeRect(rect);
960+ if (nr.IsEmpty()) {
961+ return MakeEmptyRect();
962+ }
963+ int64_t px = pivot.x;
964+ int64_t py = pivot.y;
965+ int64_t l = nr.x;
966+ int64_t t = nr.y;
967+ int64_t r = RightEdge(nr);
968+ int64_t b = BottomEdge(nr);
969+ int64_t nx = px - (b - py);
970+ int64_t ny = py + (l - px);
971+ int64_t nw = nr.height;
972+ int64_t nh = nr.width;
973+ int64_t nx2 = px - (t - py);
974+ int64_t ny2 = py + (r - px);
975+ int64_t minX = std::min(nx, nx2 - nw);
976+ int64_t minY = std::min(ny, ny2 - nh);
977+ return ClampRect(minX, minY, nw, nh);
978+}
979+ 
980+std::vector<SelectionRect> SelectionGeometryUtils::MergeOverlapping(const std::vector<SelectionRect> &list,
981+ int32_t tol)
982+{
983+ std::vector<SelectionRect> work;
984+ work.reserve(list.size());
985+ for (const auto &item : list) {
986+ if (!item.IsEmpty()) {
987+ work.push_back(NormalizeRect(item));
988+ }
989+ }
990+ bool changed = true;
991+ while (changed) {
992+ changed = false;
993+ for (size_t i = 0; i < work.size(); ++i) {
994+ size_t j = FindMergePartner(work, i, tol);
995+ if (j >= work.size()) {
996+ continue;
997+ }
998+ work[i] = UnionRect(work[i], work[j]);
999+ work.erase(work.begin() + static_cast<ptrdiff_t>(j));
1000+ changed = true;
1001+ break;
1002+ }
1003+ }
1004+ return work;
1005+}
1006+ 
1007+int64_t SelectionGeometryUtils::TotalArea(const std::vector<SelectionRect> &list)
1008+{
1009+ int64_t total = 0;
1010+ for (const auto &item : list) {
1011+ SelectionRect nr = NormalizeRect(item);
1012+ total += static_cast<int64_t>(AbsInt64(nr.width)) * static_cast<int64_t>(AbsInt64(nr.height));
1013+ }
1014+ return total;
1015+}
1016+ 
1017+SelectionRect SelectionGeometryUtils::BoundsOfList(const std::vector<SelectionRect> &list)
1018+{
1019+ return UnionListBounds(list);
1020+}
1021+ 
1022+bool SelectionGeometryUtils::ContainsAny(const std::vector<SelectionRect> &list, const SelectionRect &rect)
1023+{
1024+ for (const auto &item : list) {
1025+ if (ContainsRect(item, rect)) {
1026+ return true;
1027+ }
1028+ }
1029+ return false;
1030+}
1031+ 
1032+bool SelectionGeometryUtils::ContainsAll(const std::vector<SelectionRect> &list, const SelectionRect &rect)
1033+{
1034+ if (list.empty()) {
1035+ return false;
1036+ }
1037+ for (const auto &item : list) {
1038+ if (!ContainsRect(item, rect)) {
1039+ return false;
1040+ }
1041+ }
1042+ return true;
1043+}
1044+ 
1045+std::vector<SelectionRect> SelectionGeometryUtils::RemoveContained(const std::vector<SelectionRect> &list)
1046+{
1047+ std::vector<SelectionRect> normed;
1048+ normed.reserve(list.size());
1049+ for (const auto &item : list) {
1050+ if (!item.IsEmpty()) {
1051+ normed.push_back(NormalizeRect(item));
1052+ }
1053+ }
1054+ std::vector<SelectionRect> result;
1055+ result.reserve(normed.size());
1056+ for (size_t i = 0; i < normed.size(); ++i) {
1057+ if (!IsContainedByAny(i, normed)) {
1058+ result.push_back(normed[i]);
1059+ }
1060+ }
1061+ return result;
1062+}
1063+ 
1064+std::vector<SelectionRect> SelectionGeometryUtils::Deduplicate(const std::vector<SelectionRect> &list,
1065+ int32_t tol)
1066+{
1067+ std::vector<SelectionRect> normed;
1068+ normed.reserve(list.size());
1069+ for (const auto &item : list) {
1070+ if (!item.IsEmpty()) {
1071+ normed.push_back(NormalizeRect(item));
1072+ }
1073+ }
1074+ std::vector<SelectionRect> result;
1075+ result.reserve(normed.size());
1076+ for (const auto &item : normed) {
1077+ bool dup = false;
1078+ for (const auto &kept : result) {
1079+ if (EqualsTolerance(item, kept, tol)) {
1080+ dup = true;
1081+ break;
1082+ }
1083+ }
1084+ if (!dup) {
1085+ result.push_back(item);
1086+ }
1087+ }
1088+ return result;
1089+}
1090+ 
1091+std::vector<std::vector<SelectionRect>> SelectionGeometryUtils::GroupByRows(
1092+ const std::vector<SelectionRect> &list, int32_t rowTol)
1093+{
1094+ std::vector<SelectionRect> sorted = SortByRow(list);
1095+ std::vector<std::vector<SelectionRect>> groups;
1096+ for (const auto &rect : sorted) {
1097+ if (rect.IsEmpty()) {
1098+ continue;
1099+ }
1100+ size_t idx = FindRowGroup(rect, groups, rowTol);
1101+ if (idx >= groups.size()) {
1102+ groups.push_back(std::vector<SelectionRect>{rect});
1103+ } else {
1104+ groups[idx].push_back(rect);
1105+ }
1106+ }
1107+ return groups;
1108+}
1109+ 
1110+std::vector<SelectionRect> SelectionGeometryUtils::SortByRow(const std::vector<SelectionRect> &list)
1111+{
1112+ std::vector<SelectionRect> normed;
1113+ normed.reserve(list.size());
1114+ for (const auto &item : list) {
1115+ if (!item.IsEmpty()) {
1116+ normed.push_back(NormalizeRect(item));
1117+ }
1118+ }
1119+ std::sort(normed.begin(), normed.end(), [](const SelectionRect &a, const SelectionRect &b) {
1120+ if (a.y != b.y) {
1121+ return a.y < b.y;
1122+ }
1123+ if (a.x != b.x) {
1124+ return a.x < b.x;
1125+ }
1126+ if (a.height != b.height) {
1127+ return a.height < b.height;
1128+ }
1129+ return a.width < b.width;
1130+ });
1131+ return normed;
1132+}
1133+ 
1134+SelectionRect SelectionGeometryUtils::ClipToBounds(const SelectionRect &rect, const SelectionRect &bounds)
1135+{
1136+ return IntersectRect(rect, bounds);
1137+}
1138+ 
1139+std::vector<SelectionRect> SelectionGeometryUtils::ClipRectListToBounds(const std::vector<SelectionRect> &list,
1140+ const SelectionRect &bounds)
1141+{
1142+ return IntersectListWithRect(list, bounds);
1143+}
1144+ 
1145+bool SelectionGeometryUtils::ClipSegmentToRect(SelectionPoint &p1, SelectionPoint &p2,
1146+ const SelectionRect &rect)
1147+{
1148+ if (rect.IsEmpty()) {
1149+ return false;
1150+ }
1151+ SelectionRect nr = NormalizeRect(rect);
1152+ double x1 = static_cast<double>(p1.x);
1153+ double y1 = static_cast<double>(p1.y);
1154+ double x2 = static_cast<double>(p2.x);
1155+ double y2 = static_cast<double>(p2.y);
1156+ double dx = x2 - x1;
1157+ double dy = y2 - y1;
1158+ double p[RECT_EDGE_COUNT] = { -dx, dx, -dy, dy };
1159+ double q[RECT_EDGE_COUNT] = { x1 - static_cast<double>(nr.x),
1160+ static_cast<double>(RightEdge(nr)) - x1,
1161+ y1 - static_cast<double>(nr.y),
1162+ static_cast<double>(BottomEdge(nr)) - y1 };
1163+ double t0 = CLIP_T_MIN;
1164+ double t1 = CLIP_T_MAX;
1165+ for (int i = 0; i < RECT_EDGE_COUNT; ++i) {
1166+ if (!ApplySegmentClipEdge(p[i], q[i], t0, t1)) {
1167+ return false;
1168+ }
1169+ }
1170+ double nx1 = x1 + t0 * dx;
1171+ double ny1 = y1 + t0 * dy;
1172+ double nx2 = x1 + t1 * dx;
1173+ double ny2 = y1 + t1 * dy;
1174+ p1.x = ClampToInt32(static_cast<int64_t>(std::round(nx1)));
1175+ p1.y = ClampToInt32(static_cast<int64_t>(std::round(ny1)));
1176+ p2.x = ClampToInt32(static_cast<int64_t>(std::round(nx2)));
1177+ p2.y = ClampToInt32(static_cast<int64_t>(std::round(ny2)));
1178+ return true;
1179+}
1180+ 
1181+bool SelectionGeometryUtils::SegmentIntersectsRect(const SelectionPoint &p1, const SelectionPoint &p2,
1182+ const SelectionRect &rect)
1183+{
1184+ if (rect.IsEmpty()) {
1185+ return false;
1186+ }
1187+ SelectionRect nr = NormalizeRect(rect);
1188+ SelectionPoint s1 = p1;
1189+ SelectionPoint s2 = p2;
1190+ if (ClipSegmentToRect(s1, s2, nr)) {
1191+ return true;
1192+ }
1193+ return ContainsPoint(nr, p1) || ContainsPoint(nr, p2);
1194+}
1195+ 
1196+std::vector<SelectionPoint> SelectionGeometryUtils::ClipPolygonToRect(
1197+ const std::vector<SelectionPoint> &polygon, const SelectionRect &rect)
1198+{
1199+ if (polygon.size() < MIN_POLYGON_VERTICES || rect.IsEmpty()) {
1200+ return std::vector<SelectionPoint>();
1201+ }
1202+ SelectionRect nr = NormalizeRect(rect);
1203+ std::vector<SelectionPoint> output = polygon;
1204+ for (int edge = 0; edge < RECT_EDGE_COUNT; ++edge) {
1205+ output = ClipPolygonSingleEdge(output, edge, nr);
1206+ if (output.empty()) {
1207+ return output;
1208+ }
1209+ }
1210+ return output;
1211+}
1212+ 
1213+int64_t SelectionGeometryUtils::UnionArea(const std::vector<SelectionRect> &list)
1214+{
1215+ if (list.empty()) {
1216+ return 0;
1217+ }
1218+ std::vector<SelectionRect> normed;
1219+ normed.reserve(list.size());
1220+ for (const auto &item : list) {
1221+ if (!item.IsEmpty()) {
1222+ normed.push_back(NormalizeRect(item));
1223+ }
1224+ }
1225+ if (normed.empty()) {
1226+ return 0;
1227+ }
1228+ std::vector<int64_t> ys;
1229+ ys.reserve(normed.size() * Y_BOUNDS_PER_RECT);
1230+ for (const auto &rect : normed) {
1231+ ys.push_back(rect.y);
1232+ ys.push_back(BottomEdge(rect));
1233+ }
1234+ std::sort(ys.begin(), ys.end());
1235+ ys.erase(std::unique(ys.begin(), ys.end()), ys.end());
1236+ int64_t total = 0;
1237+ for (size_t i = 0; i + 1 < ys.size(); ++i) {
1238+ int64_t y0 = ys[i];
1239+ int64_t y1 = ys[i + 1];
1240+ if (y1 <= y0) {
1241+ continue;
1242+ }
1243+ std::vector<Interval> intervals;
1244+ for (const auto &rect : normed) {
1245+ if (rect.y <= y0 && BottomEdge(rect) >= y1) {
1246+ intervals.push_back({ rect.x, RightEdge(rect) });
1247+ }
1248+ }
1249+ if (intervals.empty()) {
1250+ continue;
1251+ }
1252+ total += MergeIntervalLengths(intervals) * (y1 - y0);
1253+ }
1254+ return total;
1255+}
1256+ 
1257+SelectionRect SelectionGeometryUtils::MapFromParent(const SelectionRect &rect,
1258+ const SelectionPoint &parentOrigin)
1259+{
1260+ SelectionRect nr = NormalizeRect(rect);
1261+ if (nr.IsEmpty()) {
1262+ return MakeEmptyRect();
1263+ }
1264+ int64_t x = static_cast<int64_t>(nr.x) - static_cast<int64_t>(parentOrigin.x);
1265+ int64_t y = static_cast<int64_t>(nr.y) - static_cast<int64_t>(parentOrigin.y);
1266+ return ClampRect(x, y, nr.width, nr.height);
1267+}
1268+ 
1269+SelectionRect SelectionGeometryUtils::MapToParent(const SelectionRect &rect,
1270+ const SelectionPoint &parentOrigin)
1271+{
1272+ SelectionRect nr = NormalizeRect(rect);
1273+ if (nr.IsEmpty()) {
1274+ return MakeEmptyRect();
1275+ }
1276+ int64_t x = static_cast<int64_t>(nr.x) + static_cast<int64_t>(parentOrigin.x);
1277+ int64_t y = static_cast<int64_t>(nr.y) + static_cast<int64_t>(parentOrigin.y);
1278+ return ClampRect(x, y, nr.width, nr.height);
1279+}
1280+ 
1281+SelectionRect SelectionGeometryUtils::NormalizeForRTL(const SelectionRect &rect, int32_t parentWidth)
1282+{
1283+ SelectionRect nr = NormalizeRect(rect);
1284+ if (nr.IsEmpty() || parentWidth <= 0) {
1285+ return nr;
1286+ }
1287+ int64_t pw = static_cast<int64_t>(parentWidth);
1288+ int64_t l = nr.x;
1289+ int64_t r = RightEdge(nr);
1290+ int64_t newR = pw - l;
1291+ int64_t newL = pw - r;
1292+ if (newL > newR) {
1293+ std::swap(newL, newR);
1294+ }
1295+ return ClampRect(newL, nr.y, newR - newL, nr.height);
1296+}
1297+ 
1298+SelectionRect SelectionGeometryUtils::MinRectByArea(const SelectionRect &a, const SelectionRect &b)
1299+{
1300+ if (a.IsEmpty()) {
1301+ return b;
1302+ }
1303+ if (b.IsEmpty()) {
1304+ return a;
1305+ }
1306+ return a.Area() <= b.Area() ? a : b;
1307+}
1308+ 
1309+SelectionRect SelectionGeometryUtils::MaxRectByArea(const SelectionRect &a, const SelectionRect &b)
1310+{
1311+ if (a.IsEmpty()) {
1312+ return b;
1313+ }
1314+ if (b.IsEmpty()) {
1315+ return a;
1316+ }
1317+ return a.Area() >= b.Area() ? a : b;
1318+}
1319+ 
1320+double SelectionGeometryUtils::PointDistance(const SelectionPoint &a, const SelectionPoint &b)
1321+{
1322+ double dx = static_cast<double>(a.x - b.x);
1323+ double dy = static_cast<double>(a.y - b.y);
1324+ return std::sqrt(dx * dx + dy * dy);
1325+}
1326+ 
1327+bool SelectionGeometryUtils::PointEqualsTolerance(const SelectionPoint &a, const SelectionPoint &b,
1328+ int32_t tol)
1329+{
1330+ int32_t absTol = tol < 0 ? -tol : tol;
1331+ int32_t dx = SafeSubInt32(a.x, b.x);
1332+ if (dx < 0) {
1333+ dx = -dx;
1334+ }
1335+ int32_t dy = SafeSubInt32(a.y, b.y);
1336+ if (dy < 0) {
1337+ dy = -dy;
1338+ }
1339+ return dx <= absTol && dy <= absTol;
1340+}
1341+ 
1342+SelectionPoint SelectionGeometryUtils::PointLerp(const SelectionPoint &a, const SelectionPoint &b,
1343+ double t)
1344+{
1345+ if (t < CLIP_T_MIN) {
1346+ t = CLIP_T_MIN;
1347+ } else if (t > CLIP_T_MAX) {
1348+ t = CLIP_T_MAX;
1349+ }
1350+ double x = static_cast<double>(a.x) + t * static_cast<double>(b.x - a.x);
1351+ double y = static_cast<double>(a.y) + t * static_cast<double>(b.y - a.y);
1352+ return SelectionPoint(SnapToPixel(x), SnapToPixel(y));
1353+}
1354+ 
1355+SelectionPoint SelectionGeometryUtils::PointTranslate(const SelectionPoint &pt, int32_t dx, int32_t dy)
1356+{
1357+ int64_t x = static_cast<int64_t>(pt.x) + static_cast<int64_t>(dx);
1358+ int64_t y = static_cast<int64_t>(pt.y) + static_cast<int64_t>(dy);
1359+ return SelectionPoint(ClampToInt32(x), ClampToInt32(y));
1360+}
1361+ 
1362+SelectionPoint SelectionGeometryUtils::PointRotate90(const SelectionPoint &pt, const SelectionPoint &pivot)
1363+{
1364+ int64_t dx = static_cast<int64_t>(pt.x) - static_cast<int64_t>(pivot.x);
1365+ int64_t dy = static_cast<int64_t>(pt.y) - static_cast<int64_t>(pivot.y);
1366+ int64_t nx = static_cast<int64_t>(pivot.x) - dy;
1367+ int64_t ny = static_cast<int64_t>(pivot.y) + dx;
1368+ return SelectionPoint(ClampToInt32(nx), ClampToInt32(ny));
1369+}
1370+ 
1371+SelectionPoint SelectionGeometryUtils::PointMirrorHorizontal(const SelectionPoint &pt, int32_t axisX)
1372+{
1373+ int64_t axis = static_cast<int64_t>(axisX);
1374+ int64_t x = static_cast<int64_t>(pt.x);
1375+ int64_t nx = MIRROR_FACTOR * axis - x;
1376+ return SelectionPoint(ClampToInt32(nx), pt.y);
1377+}
1378+ 
1379+SelectionPoint SelectionGeometryUtils::PointMirrorVertical(const SelectionPoint &pt, int32_t axisY)
1380+{
1381+ int64_t axis = static_cast<int64_t>(axisY);
1382+ int64_t y = static_cast<int64_t>(pt.y);
1383+ int64_t ny = MIRROR_FACTOR * axis - y;
1384+ return SelectionPoint(pt.x, ClampToInt32(ny));
1385+}
1386+ 
1387+double SelectionGeometryUtils::SegmentLength(const SelectionPoint &a, const SelectionPoint &b)
1388+{
1389+ return PointDistance(a, b);
1390+}
1391+ 
1392+double SelectionGeometryUtils::PointToSegmentDistance(const SelectionPoint &p, const SelectionPoint &s,
1393+ const SelectionPoint &e)
1394+{
1395+ int64_t px = p.x;
1396+ int64_t py = p.y;
1397+ int64_t sx = s.x;
1398+ int64_t sy = s.y;
1399+ int64_t ex = e.x;
1400+ int64_t ey = e.y;
1401+ int64_t dx = ex - sx;
1402+ int64_t dy = ey - sy;
1403+ int64_t lenSq = dx * dx + dy * dy;
1404+ if (lenSq == 0) {
1405+ double ddx = static_cast<double>(px - sx);
1406+ double ddy = static_cast<double>(py - sy);
1407+ return std::sqrt(ddx * ddx + ddy * ddy);
1408+ }
1409+ int64_t crossX = px - sx;
1410+ int64_t crossY = py - sy;
1411+ double t = static_cast<double>(crossX * dx + crossY * dy) / static_cast<double>(lenSq);
1412+ if (t < CLIP_T_MIN) {
1413+ t = CLIP_T_MIN;
1414+ } else if (t > CLIP_T_MAX) {
1415+ t = CLIP_T_MAX;
1416+ }
1417+ double projX = static_cast<double>(sx) + t * static_cast<double>(dx);
1418+ double projY = static_cast<double>(sy) + t * static_cast<double>(dy);
1419+ double ddx = static_cast<double>(px) - projX;
1420+ double ddy = static_cast<double>(py) - projY;
1421+ return std::sqrt(ddx * ddx + ddy * ddy);
1422+}
1423+ 
1424+SelectionPoint SelectionGeometryUtils::FootOfPerpendicular(const SelectionPoint &p, const SelectionPoint &s,
1425+ const SelectionPoint &e)
1426+{
1427+ int64_t sx = s.x;
1428+ int64_t sy = s.y;
1429+ int64_t ex = e.x;
1430+ int64_t ey = e.y;
1431+ int64_t dx = ex - sx;
1432+ int64_t dy = ey - sy;
1433+ int64_t lenSq = dx * dx + dy * dy;
1434+ if (lenSq == 0) {
1435+ return s;
1436+ }
1437+ int64_t crossX = static_cast<int64_t>(p.x) - sx;
1438+ int64_t crossY = static_cast<int64_t>(p.y) - sy;
1439+ double t = static_cast<double>(crossX * dx + crossY * dy) / static_cast<double>(lenSq);
1440+ double projX = static_cast<double>(sx) + t * static_cast<double>(dx);
1441+ double projY = static_cast<double>(sy) + t * static_cast<double>(dy);
1442+ return SelectionPoint(SnapToPixel(projX), SnapToPixel(projY));
1443+}
1444+ 
1445+bool SelectionGeometryUtils::IsPointOnSegment(const SelectionPoint &p, const SelectionPoint &s,
1446+ const SelectionPoint &e)
1447+{
1448+ int64_t px = p.x;
1449+ int64_t py = p.y;
1450+ int64_t sx = s.x;
1451+ int64_t sy = s.y;
1452+ int64_t ex = e.x;
1453+ int64_t ey = e.y;
1454+ int64_t cross = (ex - sx) * (py - sy) - (ey - sy) * (px - sx);
1455+ if (cross != 0) {
1456+ return false;
1457+ }
1458+ int64_t dot = (px - sx) * (px - ex) + (py - sy) * (py - ey);
1459+ return dot <= 0;
1460+}
1461+ 
1462+std::vector<SelectionPoint> SelectionGeometryUtils::RectCorners(const SelectionRect &rect)
1463+{
1464+ SelectionRect nr = NormalizeRect(rect);
1465+ if (nr.IsEmpty()) {
1466+ return std::vector<SelectionPoint>();
1467+ }
1468+ int32_t l = nr.x;
1469+ int32_t t = nr.y;
1470+ int32_t r = ClampToInt32(RightEdge(nr));
1471+ int32_t b = ClampToInt32(BottomEdge(nr));
1472+ return std::vector<SelectionPoint>{
1473+ SelectionPoint(l, t),
1474+ SelectionPoint(r, t),
1475+ SelectionPoint(r, b),
1476+ SelectionPoint(l, b),
1477+ };
1478+}
1479+ 
1480+std::vector<SelectionPoint> SelectionGeometryUtils::RectEdgeMidpoints(const SelectionRect &rect)
1481+{
1482+ SelectionRect nr = NormalizeRect(rect);
1483+ if (nr.IsEmpty()) {
1484+ return std::vector<SelectionPoint>();
1485+ }
1486+ int64_t cx = CenterX(nr);
1487+ int64_t cy = CenterY(nr);
1488+ int32_t l = nr.x;
1489+ int32_t t = nr.y;
1490+ int32_t r = ClampToInt32(RightEdge(nr));
1491+ int32_t b = ClampToInt32(BottomEdge(nr));
1492+ return std::vector<SelectionPoint>{
1493+ SelectionPoint(static_cast<int32_t>(cx), t),
1494+ SelectionPoint(r, static_cast<int32_t>(cy)),
1495+ SelectionPoint(static_cast<int32_t>(cx), b),
1496+ SelectionPoint(l, static_cast<int32_t>(cy)),
1497+ };
1498+}
1499+ 
1500+int32_t SelectionGeometryUtils::ClosestEdgeIndex(const SelectionRect &rect, const SelectionPoint &point)
1501+{
1502+ SelectionRect nr = NormalizeRect(rect);
1503+ if (nr.IsEmpty()) {
1504+ return -1;
1505+ }
1506+ int64_t px = point.x;
1507+ int64_t py = point.y;
1508+ int64_t l = nr.x;
1509+ int64_t t = nr.y;
1510+ int64_t r = RightEdge(nr);
1511+ int64_t b = BottomEdge(nr);
1512+ int64_t cx = CenterX(nr);
1513+ int64_t cy = CenterY(nr);
1514+ int64_t dTop = AbsInt64(py - t);
1515+ int64_t dBottom = AbsInt64(py - b);
1516+ int64_t dLeft = AbsInt64(px - l);
1517+ int64_t dRight = AbsInt64(px - r);
1518+ int64_t dists[RECT_EDGE_COUNT] = { dTop, dRight, dBottom, dLeft };
1519+ int64_t cxToCenter = AbsInt64(px - cx);
1520+ int64_t cyToCenter = AbsInt64(py - cy);
1521+ int64_t extraTop = (py < t) ? cyToCenter : 0;
1522+ int64_t extraBottom = (py >= b) ? cyToCenter : 0;
1523+ int64_t extraLeft = (px < l) ? cxToCenter : 0;
1524+ int64_t extraRight = (px >= r) ? cxToCenter : 0;
1525+ int64_t extras[RECT_EDGE_COUNT] = { extraTop, extraRight, extraBottom, extraLeft };
1526+ int32_t best = 0;
1527+ for (int i = 1; i < RECT_EDGE_COUNT; ++i) {
1528+ int64_t cur = dists[i] + extras[i];
1529+ int64_t bestVal = dists[best] + extras[best];
1530+ if (cur < bestVal) {
1531+ best = i;
1532+ }
1533+ }
1534+ return best;
1535+}
1536+ 
1537+SelectionRect SelectionGeometryUtils::ExpandToContain(const SelectionRect &rect, const SelectionPoint &point)
1538+{
1539+ if (rect.IsEmpty()) {
1540+ return ClampRect(point.x, point.y, 1, 1);
1541+ }
1542+ SelectionRect nr = NormalizeRect(rect);
1543+ int64_t l = std::min(static_cast<int64_t>(nr.x), static_cast<int64_t>(point.x));
1544+ int64_t t = std::min(static_cast<int64_t>(nr.y), static_cast<int64_t>(point.y));
1545+ int64_t r = std::max(RightEdge(nr), static_cast<int64_t>(point.x) + 1);
1546+ int64_t b = std::max(BottomEdge(nr), static_cast<int64_t>(point.y) + 1);
1547+ return ClampRect(l, t, r - l, b - t);
1548+}
1549+ 
1550+SelectionRect SelectionGeometryUtils::ShrinkToAspectRatio(const SelectionRect &rect, double targetRatio)
1551+{
1552+ SelectionRect nr = NormalizeRect(rect);
1553+ if (nr.IsEmpty() || targetRatio <= 0.0) {
1554+ return MakeEmptyRect();
1555+ }
1556+ int64_t w = nr.width;
1557+ int64_t h = nr.height;
1558+ if (h == 0 || targetRatio == 0.0) {
1559+ return MakeEmptyRect();
1560+ }
1561+ int64_t cx = CenterX(nr);
1562+ int64_t cy = CenterY(nr);
1563+ int64_t newW = w;
1564+ int64_t newH = h;
1565+ double curRatio = static_cast<double>(w) / static_cast<double>(h);
1566+ if (curRatio > targetRatio) {
1567+ newW = static_cast<int64_t>(std::floor(static_cast<double>(h) * targetRatio));
1568+ } else if (curRatio < targetRatio) {
1569+ newH = static_cast<int64_t>(std::floor(static_cast<double>(w) / targetRatio));
1570+ }
1571+ if (newW > w) {
1572+ newW = w;
1573+ }
1574+ if (newH > h) {
1575+ newH = h;
1576+ }
1577+ int64_t newL = cx - newW / HALF_FACTOR;
1578+ int64_t newT = cy - newH / HALF_FACTOR;
1579+ return ClampRect(newL, newT, newW, newH);
1580+}
1581+ 
1582+std::vector<SelectionRect> SelectionGeometryUtils::SplitGrid(const SelectionRect &rect, int32_t rows,
1583+ int32_t cols)
1584+{
1585+ std::vector<SelectionRect> result;
1586+ if (rect.IsEmpty()) {
1587+ return result;
1588+ }
1589+ SelectionRect nr = NormalizeRect(rect);
1590+ if (cols <= 0 || rows <= 0) {
1591+ return result;
1592+ }
1593+ int64_t totalW = nr.width;
1594+ int64_t totalH = nr.height;
1595+ int64_t baseW = totalW / static_cast<int64_t>(cols);
1596+ int64_t baseH = totalH / static_cast<int64_t>(rows);
1597+ int64_t remW = totalW - baseW * static_cast<int64_t>(cols);
1598+ int64_t remH = totalH - baseH * static_cast<int64_t>(rows);
1599+ int64_t y = nr.y;
1600+ for (int32_t r = 0; r < rows; ++r) {
1601+ int64_t cellH = baseH + (r < remH ? 1 : 0);
1602+ int64_t x = nr.x;
1603+ for (int32_t c = 0; c < cols; ++c) {
1604+ int64_t cellW = baseW + (c < remW ? 1 : 0);
1605+ result.push_back(ClampRect(x, y, cellW, cellH));
1606+ x += cellW;
1607+ }
1608+ y += cellH;
1609+ }
1610+ return result;
1611+}
1612+ 
1613+bool SelectionGeometryUtils::IsSquare(const SelectionRect &rect)
1614+{
1615+ SelectionRect nr = NormalizeRect(rect);
1616+ if (nr.IsEmpty()) {
1617+ return false;
1618+ }
1619+ return nr.width == nr.height;
1620+}
1621+ 
1622+int32_t SelectionGeometryUtils::LargerAxis(const SelectionRect &rect)
1623+{
1624+ SelectionRect nr = NormalizeRect(rect);
1625+ if (nr.IsEmpty()) {
1626+ return 0;
1627+ }
1628+ return nr.width >= nr.height ? nr.width : nr.height;
1629+}
1630+ 
1631+int32_t SelectionGeometryUtils::SmallerAxis(const SelectionRect &rect)
1632+{
1633+ SelectionRect nr = NormalizeRect(rect);
1634+ if (nr.IsEmpty()) {
1635+ return 0;
1636+ }
1637+ return nr.width <= nr.height ? nr.width : nr.height;
1638+}
1639+ 
1640+SelectionRect SelectionGeometryUtils::CenterAt(const SelectionRect &rect, const SelectionPoint &center)
1641+{
1642+ SelectionRect nr = NormalizeRect(rect);
1643+ if (nr.IsEmpty()) {
1644+ return MakeEmptyRect();
1645+ }
1646+ int64_t cx = center.x;
1647+ int64_t cy = center.y;
1648+ int64_t newL = cx - static_cast<int64_t>(nr.width) / HALF_FACTOR;
1649+ int64_t newT = cy - static_cast<int64_t>(nr.height) / HALF_FACTOR;
1650+ return ClampRect(newL, newT, nr.width, nr.height);
1651+}
1652+ 
1653+void SelectionRectCombiner::Add(const SelectionRect &rect)
1654+{
1655+ if (rect.IsEmpty()) {
1656+ return;
1657+ }
1658+ rects_.push_back(SelectionGeometryUtils::NormalizeRect(rect));
1659+ TryMergeAdjacent();
1660+}
1661+ 
1662+std::vector<SelectionRect> SelectionRectCombiner::Result() const
1663+{
1664+ return rects_;
1665+}
1666+ 
1667+void SelectionRectCombiner::Clear()
1668+{
1669+ rects_.clear();
1670+}
1671+ 
1672+size_t SelectionRectCombiner::Size() const
1673+{
1674+ return rects_.size();
1675+}
1676+ 
1677+bool SelectionRectCombiner::Empty() const
1678+{
1679+ return rects_.empty();
1680+}
1681+ 
1682+void SelectionRectCombiner::TryMergeAdjacent()
1683+{
1684+ bool changed = true;
1685+ while (changed) {
1686+ changed = false;
1687+ for (size_t i = 0; i < rects_.size(); ++i) {
1688+ size_t j = FindMergePartner(rects_, i, 0);
1689+ if (j >= rects_.size()) {
1690+ continue;
1691+ }
1692+ rects_[i] = SelectionGeometryUtils::UnionRect(rects_[i], rects_[j]);
1693+ rects_.erase(rects_.begin() + static_cast<ptrdiff_t>(j));
1694+ changed = true;
1695+ break;
1696+ }
1697+ }
1698+}
1699+ 
1700+} // namespace SelectionFwk
1701+} // namespace OHOS