chromium/ios/chrome/browser/shared/ui/util/rtl_geometry.mm

// 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];
}