// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/shared/ui/util/rtl_geometry.h"
#import <UIKit/UIKit.h>
#import <limits>
#import "base/check_op.h"
#import "ios/chrome/common/ui/util/ui_util.h"
bool UseRTLLayout() {
return base::i18n::IsRTL();
}
base::i18n::TextDirection LayoutDirection() {
return UseRTLLayout() ? base::i18n::RIGHT_TO_LEFT : base::i18n::LEFT_TO_RIGHT;
}
#pragma mark - LayoutRects.
constexpr const LayoutRectPosition LayoutRectPositionZero = {0.0, 0.0};
LayoutRectPosition LayoutRectPositionMake(CGFloat leading, CGFloat originY) {
return {leading, originY};
}
BOOL LayoutRectPositionEqualToPosition(LayoutRectPosition a,
LayoutRectPosition b) {
CGFloat epsilon = std::numeric_limits<CGFloat>::epsilon();
return fabs(a.leading - b.leading) <= epsilon &&
fabs(a.originY - b.originY) <= epsilon;
}
LayoutRectPosition AlignLayoutRectPositionToPixel(LayoutRectPosition position) {
return LayoutRectPositionMake(AlignValueToPixel(position.leading),
AlignValueToPixel(position.originY));
}
const LayoutRect LayoutRectZero = {0.0, {0.0, 0.0}, {0.0, 0.0}};
LayoutRect LayoutRectMake(CGFloat leading,
CGFloat boundingWidth,
CGFloat originY,
CGFloat width,
CGFloat height) {
return {boundingWidth, LayoutRectPositionMake(leading, originY),
CGSizeMake(width, height)};
}
BOOL LayoutRectEqualToRect(LayoutRect a, LayoutRect b) {
CGFloat epsilon = std::numeric_limits<CGFloat>::epsilon();
return fabs(a.boundingWidth - b.boundingWidth) <= epsilon &&
LayoutRectPositionEqualToPosition(a.position, b.position) &&
CGSizeEqualToSize(a.size, b.size);
}
CGRect LayoutRectGetRectUsingDirection(LayoutRect layout,
base::i18n::TextDirection direction) {
CGRect rect;
if (direction == base::i18n::RIGHT_TO_LEFT) {
CGFloat trailing =
layout.boundingWidth - (layout.position.leading + layout.size.width);
rect = CGRectMake(trailing, layout.position.originY, layout.size.width,
layout.size.height);
} else {
DCHECK_EQ(direction, base::i18n::LEFT_TO_RIGHT);
rect = CGRectMake(layout.position.leading, layout.position.originY,
layout.size.width, layout.size.height);
}
return rect;
}
CGRect LayoutRectGetRect(LayoutRect layout) {
return LayoutRectGetRectUsingDirection(layout, LayoutDirection());
}
CGRect LayoutRectGetBoundsRect(LayoutRect layout) {
return CGRectMake(0, 0, layout.size.width, layout.size.height);
}
CGPoint LayoutRectGetPositionForAnchorUsingDirection(
LayoutRect layout,
CGPoint anchor,
base::i18n::TextDirection direction) {
CGRect rect = LayoutRectGetRectUsingDirection(layout, direction);
return CGPointMake(CGRectGetMinX(rect) + (rect.size.width * anchor.x),
CGRectGetMinY(rect) + (rect.size.height * anchor.y));
}
CGPoint LayoutRectGetPositionForAnchor(LayoutRect layout, CGPoint anchor) {
return LayoutRectGetPositionForAnchorUsingDirection(layout, anchor,
LayoutDirection());
}
LayoutRect LayoutRectForRectInBoundingRectUsingDirection(
CGRect rect,
CGRect contextRect,
base::i18n::TextDirection direction) {
LayoutRect layout;
if (direction == base::i18n::RIGHT_TO_LEFT) {
layout.position.leading = contextRect.size.width - CGRectGetMaxX(rect);
} else {
layout.position.leading = CGRectGetMinX(rect);
}
layout.boundingWidth = contextRect.size.width;
layout.position.originY = rect.origin.y;
layout.size = rect.size;
return layout;
}
LayoutRect LayoutRectForRectInBoundingRect(CGRect rect, CGRect contextRect) {
return LayoutRectForRectInBoundingRectUsingDirection(rect, contextRect,
LayoutDirection());
}
LayoutRect LayoutRectGetLeadingLayout(LayoutRect layout) {
LayoutRect leadingLayout;
leadingLayout.position.leading = 0;
leadingLayout.boundingWidth = layout.boundingWidth;
leadingLayout.position.originY = layout.position.originY;
leadingLayout.size = CGSizeMake(layout.position.leading, layout.size.height);
return leadingLayout;
}
LayoutRect LayoutRectGetTrailingLayout(LayoutRect layout) {
LayoutRect leadingLayout;
CGFloat trailing = LayoutRectGetTrailingEdge(layout);
leadingLayout.position.leading = trailing;
leadingLayout.boundingWidth = layout.boundingWidth;
leadingLayout.position.originY = layout.position.originY;
leadingLayout.size =
CGSizeMake((layout.boundingWidth - trailing), layout.size.height);
return leadingLayout;
}
CGFloat LayoutRectGetTrailingEdge(LayoutRect layout) {
return layout.position.leading + layout.size.width;
}
CGPoint CGPointLayoutOffsetUsingDirection(CGPoint point,
LayoutOffset offset,
base::i18n::TextDirection direction) {
CGPoint newPoint = point;
if (direction == base::i18n::RIGHT_TO_LEFT) {
offset = -offset;
}
newPoint.x += offset;
return newPoint;
}
CGPoint CGPointLayoutOffset(CGPoint point, LayoutOffset offset) {
return CGPointLayoutOffsetUsingDirection(point, offset, LayoutDirection());
}
CGRect CGRectLayoutOffsetUsingDirection(CGRect rect,
LayoutOffset offset,
base::i18n::TextDirection direction) {
if (direction == base::i18n::RIGHT_TO_LEFT) {
offset = -offset;
}
return CGRectOffset(rect, offset, 0);
}
CGRect CGRectLayoutOffset(CGRect rect, LayoutOffset offset) {
return CGRectLayoutOffsetUsingDirection(rect, offset, LayoutDirection());
}
LayoutOffset CGRectGetLeadingLayoutOffsetInBoundingRect(CGRect rect,
CGRect boundingRect) {
CGFloat rectLeadingEdge = CGRectGetLeadingEdge(rect);
CGFloat boundingRectLeadingEdge = CGRectGetLeadingEdge(boundingRect);
LayoutOffset offset = 0;
if (LayoutDirection() == base::i18n::LEFT_TO_RIGHT) {
// Leading edges have low x-values for LTR, so subtract the bounding rect's
// from `rect`'s.
offset = rectLeadingEdge - boundingRectLeadingEdge;
} else {
DCHECK_EQ(LayoutDirection(), base::i18n::RIGHT_TO_LEFT);
offset = boundingRectLeadingEdge - rectLeadingEdge;
}
return offset;
}
LayoutOffset CGRectGetTrailingLayoutOffsetInBoundingRect(CGRect rect,
CGRect boundingRect) {
CGFloat rectTrailingEdge = CGRectGetTrailingEdge(rect);
CGFloat boundingRectTrailingEdge = CGRectGetTrailingEdge(boundingRect);
LayoutOffset offset = 0;
if (LayoutDirection() == base::i18n::RIGHT_TO_LEFT) {
// Trailing edges have low x-values for RTL, so subtract the bounding rect's
// from `rect`'s.
offset = rectTrailingEdge - boundingRectTrailingEdge;
} else {
DCHECK_EQ(LayoutDirection(), base::i18n::LEFT_TO_RIGHT);
offset = boundingRectTrailingEdge - rectTrailingEdge;
}
return offset;
}
LayoutOffset LeadingContentOffsetForScrollView(UIScrollView* scrollView) {
return UseRTLLayout()
? scrollView.contentSize.width - scrollView.contentOffset.x -
CGRectGetWidth(scrollView.bounds)
: scrollView.contentOffset.x;
}
#pragma mark - UIKit utilities
CGFloat CGRectGetLeadingEdgeUsingDirection(
CGRect rect,
base::i18n::TextDirection direction) {
return direction == base::i18n::RIGHT_TO_LEFT ? CGRectGetMaxX(rect)
: CGRectGetMinX(rect);
}
CGFloat CGRectGetTrailingEdgeUsingDirection(
CGRect rect,
base::i18n::TextDirection direction) {
return direction == base::i18n::RIGHT_TO_LEFT ? CGRectGetMinX(rect)
: CGRectGetMaxX(rect);
}
CGFloat CGRectGetLeadingEdge(CGRect rect) {
return CGRectGetLeadingEdgeUsingDirection(rect, LayoutDirection());
}
CGFloat CGRectGetTrailingEdge(CGRect rect) {
return CGRectGetTrailingEdgeUsingDirection(rect, LayoutDirection());
}
UIViewAutoresizing UIViewAutoresizingFlexibleLeadingMargin() {
return base::i18n::IsRTL() ? UIViewAutoresizingFlexibleRightMargin
: UIViewAutoresizingFlexibleLeftMargin;
}
UIViewAutoresizing UIViewAutoresizingFlexibleTrailingMargin() {
return base::i18n::IsRTL() ? UIViewAutoresizingFlexibleLeftMargin
: UIViewAutoresizingFlexibleRightMargin;
}
UIEdgeInsets UIEdgeInsetsMakeUsingDirection(
CGFloat top,
CGFloat leading,
CGFloat bottom,
CGFloat trailing,
base::i18n::TextDirection direction) {
if (direction == base::i18n::RIGHT_TO_LEFT) {
using std::swap;
swap(leading, trailing);
}
// At this point, `leading` == left, `trailing` = right.
return UIEdgeInsetsMake(top, leading, bottom, trailing);
}
UIEdgeInsets UIEdgeInsetsMakeDirected(CGFloat top,
CGFloat leading,
CGFloat bottom,
CGFloat trailing) {
return UIEdgeInsetsMakeUsingDirection(top, leading, bottom, trailing,
LayoutDirection());
}
CGFloat UIEdgeInsetsGetLeading(UIEdgeInsets insets) {
return UseRTLLayout() ? insets.right : insets.left;
}
CGFloat UIEdgeInsetsGetTrailing(UIEdgeInsets insets) {
return UseRTLLayout() ? insets.left : insets.right;
}
BOOL EdgeLeadsEdge(CGFloat a, CGFloat b, base::i18n::TextDirection direction) {
return direction == base::i18n::RIGHT_TO_LEFT ? a > b : a < b;
}
BOOL EdgeLeadsEdge(CGFloat a, CGFloat b) {
return EdgeLeadsEdge(a, b, LayoutDirection());
}
NSTextAlignment DetermineBestAlignmentForText(NSString* text) {
if (text.length) {
NSString* lang = CFBridgingRelease(CFStringTokenizerCopyBestStringLanguage(
(CFStringRef)text, CFRangeMake(0, text.length)));
if ([NSLocale characterDirectionForLanguage:lang] ==
NSLocaleLanguageDirectionRightToLeft) {
return NSTextAlignmentRight;
}
}
return NSTextAlignmentLeft;
}
void ScrollToSemanticLeading(UIScrollView* scrollview, BOOL animated) {
BOOL isRTL = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:
scrollview.semanticContentAttribute] ==
UIUserInterfaceLayoutDirectionRightToLeft;
CGFloat contentStartX = 0;
if (isRTL) {
contentStartX = MAX(scrollview.contentSize.width - 1, 0);
}
[scrollview setContentOffset:CGPointMake(contentStartX, 0) animated:animated];
}