getConstraints method

BoxConstraints getConstraints()

Implementation

BoxConstraints getConstraints() {
  final CSSRenderStyle style = renderStyle;
  final CSSRenderStyle? attachedParentStyle =
      style.getAttachedRenderParentRenderStyle();
  final RenderBoxModel? attachedParentRenderBoxModel =
      attachedParentStyle?.attachedRenderBoxModel;
  final EdgeInsets stylePadding = style.padding;
  final EdgeInsets styleBorder = style.border;

  CSSDisplay? effectiveDisplay = style.effectiveDisplay;
  bool isDisplayInline = effectiveDisplay == CSSDisplay.inline;

  // Need to calculated logic content size on every layout.
  style.computeContentBoxLogicalWidth();
  style.computeContentBoxLogicalHeight();

  // Width should be not smaller than border and padding in horizontal direction
  // when box-sizing is border-box which is only supported.
  double minConstraintWidth =
      styleBorder.horizontal + stylePadding.horizontal;

  double? parentBoxContentConstraintsWidth;
  if (style.isParentRenderBoxModel() &&
      attachedParentRenderBoxModel != null &&
      (style.isSelfRenderLayoutBox() || style.isSelfRenderWidget())) {
    RenderBoxModel parentRenderBoxModel = attachedParentRenderBoxModel;

    // Inline-block shrink-to-fit: when the parent is inline-block with auto width,
    // do not bound block children by the parent's finite content width. This allows
    // the child to compute its own natural width and lets the parent shrink-wrap.
    bool parentIsInlineBlockAutoWidth =
        parentRenderBoxModel.renderStyle.effectiveDisplay ==
                CSSDisplay.inlineBlock &&
            parentRenderBoxModel.renderStyle.width.isAuto;

    if (parentIsInlineBlockAutoWidth) {
      parentBoxContentConstraintsWidth = double.infinity;
    } else {
      // Prefer the actual layout parent's content constraints if available,
      // because CSS parent may not yet have computed contentConstraints during IFC layout.
      BoxConstraints? candidate;
      if (parent is RenderBoxModel) {
        candidate = (parent as RenderBoxModel).contentConstraints;
      }
      candidate ??= parentRenderBoxModel.contentConstraints;

      if (candidate != null) {
        // When using CSS parent constraints, deflate with the current element's margins
        // to match previous behavior; otherwise use the raw layout parent's constraints.
        if (identical(candidate, parentRenderBoxModel.contentConstraints)) {
          parentBoxContentConstraintsWidth = parentRenderBoxModel.renderStyle
              .deflateMarginConstraints(candidate)
              .maxWidth;
        } else {
          parentBoxContentConstraintsWidth = candidate.maxWidth;
        }

        // When inner minimal content size are larger that parent's constraints,
        // still use parent constraints but ensure minConstraintWidth is properly handled later
        if (parentBoxContentConstraintsWidth < minConstraintWidth) {
          // Keep parentBoxContentConstraintsWidth; resolution happens below.
        }
      }
    }

    // Flex context adjustments
    if (style.isParentRenderFlexLayout()) {
      final RenderFlexLayout flexParent =
          attachedParentStyle!.attachedRenderBoxModel! as RenderFlexLayout;
      // FlexItems with flex:none won't inherit parent box's constraints
      if (flexParent.isFlexNone(this)) {
        parentBoxContentConstraintsWidth = null;
      }

      // In a column-direction flex container, a flex item with auto cross-size (width)
      // that is not stretched in the cross axis should not inherit the container's
      // bounded width during its own constraint computation. Let it shrink-to-fit
      // its contents instead (so percentage paddings resolve against the item's
      // own width rather than the container width).
      final bool isColumn = !CSSFlex.isHorizontalFlexDirection(
          flexParent.renderStyle.flexDirection);
      if (isColumn) {
        // Determine if this flex item would be stretched in the cross axis.
        final AlignSelf self = renderStyle.alignSelf;
        final bool parentStretch =
            flexParent.renderStyle.alignItems == AlignItems.stretch;
        final bool shouldStretch = self == AlignSelf.auto
            ? parentStretch
            : self == AlignSelf.stretch;
        final bool crossAuto = renderStyle.width.isAuto;

        if (!shouldStretch && crossAuto) {
          // Do not adopt the parent's bounded width; use intrinsic sizing.
          parentBoxContentConstraintsWidth = null;
        }
      }
    }
    // For absolutely/fixed positioned elements inside a flex container, avoid collapsing
    // their intrinsic width to 0 solely because the flex item has a content constraint
    // width of 0 (e.g., auto-width flex item whose only child is out-of-flow).
    // In such cases, the containing block still exists, but the abspos box should size
    // from its own content rather than the flex item's zero content width.
    final bool isAbsOrFixed =
        style.position == CSSPositionType.absolute ||
            style.position == CSSPositionType.fixed;
    if (isAbsOrFixed &&
        style.width.isAuto &&
        style.isParentRenderFlexLayout() &&
        parentBoxContentConstraintsWidth != null &&
        parentBoxContentConstraintsWidth == 0) {
      parentBoxContentConstraintsWidth = null;
    }
  } else if (isDisplayInline && parent is RenderFlowLayout) {
    // For inline elements inside a flow layout, check if we should inherit parent's constraints
    RenderFlowLayout parentFlow = parent as RenderFlowLayout;
    final CSSRenderStyle parentFlowStyle = parentFlow.renderStyle;

    // Skip constraint inheritance if parent is a flex item with flex: none (flex-grow: 0, flex-shrink: 0)
    if (parentFlowStyle.isParentRenderFlexLayout()) {
      RenderFlexLayout flexParent = parentFlowStyle
          .getAttachedRenderParentRenderStyle()!
          .attachedRenderBoxModel as RenderFlexLayout;
      if (flexParent.isFlexNone(parentFlow)) {
        // Don't inherit constraints for flex: none items
        parentBoxContentConstraintsWidth = null;
      } else {
        double parentContentWidth =
            parentFlowStyle.contentMaxConstraintsWidth;
        if (parentContentWidth != double.infinity) {
          parentBoxContentConstraintsWidth = parentContentWidth;
        }
      }
    } else {
      // Not in a flex context, inherit parent's content width constraint normally
      double parentContentWidth =
          parentFlowStyle.contentMaxConstraintsWidth;
      if (parentContentWidth != double.infinity) {
        parentBoxContentConstraintsWidth = parentContentWidth;
      }
    }
  }

  // CSS abspos width for non-replaced boxes with width:auto follows the
  // shrink-to-fit algorithm in CSS 2.1 ยง10.3.7 / CSS Position 3, except when
  // both left and right are non-auto (in which case width is solved directly
  // from the insets equation).
  //
  // Approximation: when width:auto and not (left & right both non-auto),
  // do not clamp the positioned box by the parent's content max-width.
  // Let the box measure to its intrinsic content width (subject to any
  // explicit min/max-width), which matches shrink-to-fit in the common
  // case where content width <= available width.
  final bool absOrFixedForWidth =
      style.position == CSSPositionType.absolute ||
          style.position == CSSPositionType.fixed;
  final bool widthAutoForAbs = style.width.isAuto;
  final bool bothLRNonAuto = style.left.isNotAuto && style.right.isNotAuto;
  if (absOrFixedForWidth &&
      !bothLRNonAuto &&
      widthAutoForAbs &&
      !style.isSelfRenderReplaced() &&
      style.borderBoxLogicalWidth == null &&
      parentBoxContentConstraintsWidth != null) {
      parentBoxContentConstraintsWidth = null;
  }

  double? containingBlockPaddingBoxWidth;
  if (absOrFixedForWidth &&
      widthAutoForAbs &&
      style.left.isNotAuto &&
      style.right.isNotAuto &&
      parent is RenderBoxModel) {
    final RenderBoxModel cb = parent as RenderBoxModel;
    final BoxConstraints? pcc = cb.contentConstraints;
    if (pcc != null && pcc.maxWidth.isFinite) {
      containingBlockPaddingBoxWidth =
          pcc.maxWidth + cb.renderStyle.padding.horizontal;
    }
  }

  double? minWidth =
      style.minWidth.isAuto ? null : style.minWidth.computedValue;
  double? maxWidth =
      style.maxWidth.isNone ? null : style.maxWidth.computedValue;
  double? minHeight = style.minHeight.isAuto
      ? null
      : style.minHeight.computedValue;
  double? maxHeight = style.maxHeight.isNone
      ? null
      : style.maxHeight.computedValue;

  double maxConstraintWidth = style.borderBoxLogicalWidth ??
      parentBoxContentConstraintsWidth ??
      double.infinity;

  // For absolutely/fixed positioned non-replaced elements with both left and right specified
  // and width:auto, compute the used border-box width from the containing block at layout time.
  // This handles cases where style-tree logical widths are unavailable (e.g., parent inline-block
  // with auto width) but the containing block has been measured. See:
  // https://www.w3.org/TR/css-position-3/#abs-non-replaced-width
  final bool isAbsOrFixed =
      style.position == CSSPositionType.absolute ||
          style.position == CSSPositionType.fixed;
  if (maxConstraintWidth == double.infinity &&
      isAbsOrFixed &&
      !style.isSelfRenderReplaced() &&
      style.width.isAuto &&
      style.left.isNotAuto &&
      style.right.isNotAuto &&
      parent is RenderBoxModel) {
    final double? parentPaddingBoxWidth = containingBlockPaddingBoxWidth;
    if (parentPaddingBoxWidth != null && parentPaddingBoxWidth.isFinite) {
      // Solve the horizontal insets equation for the child border-box width.
      double solvedBorderBoxWidth = parentPaddingBoxWidth -
          style.left.computedValue -
          style.right.computedValue -
          style.marginLeft.computedValue -
          style.marginRight.computedValue;
      // Guard against negative sizes.
      solvedBorderBoxWidth = math.max(0, solvedBorderBoxWidth);
      // Use a tight width so empty positioned boxes still fill the available space.
      minConstraintWidth = solvedBorderBoxWidth;
      maxConstraintWidth = solvedBorderBoxWidth;
    }
  }

  // Resolve intrinsic sizing keywords (min-content/max-content/fit-content) for `width`
  // at layout time using the render tree's intrinsic measurements.
  //
  // CSSLengthValue.computedValue returns 0 for intrinsic keywords, so treating them as
  // definite lengths during style computation collapses boxes to padding/border only.
  // Here we compute the used border-box width and tighten constraints to that value.
  if (style.width.isIntrinsic &&
      !isDisplayInline &&
      // For non-replaced boxes, intrinsic widths come from layout content.
      !style.isSelfRenderReplaced() &&
      // Absolutely positioned boxes with both insets specified should be solved by insets.
      !(isAbsOrFixed &&
          style.left.isNotAuto &&
          style.right.isNotAuto &&
          style.width.isAuto)) {
    // Use the parent's available inline size if it's definite; otherwise fall back to infinity.
    final double available = (parentBoxContentConstraintsWidth != null &&
            parentBoxContentConstraintsWidth.isFinite)
        ? parentBoxContentConstraintsWidth
        : (maxConstraintWidth.isFinite
            ? maxConstraintWidth
            : double.infinity);

    double minIntrinsic = getMinIntrinsicWidth(double.infinity);
    double maxIntrinsic = getMaxIntrinsicWidth(double.infinity);

    // Respect nowrap/pre: min-content equals max-content for unbreakable inline content.
    if (style.whiteSpace == WhiteSpace.nowrap ||
        style.whiteSpace == WhiteSpace.pre) {
      minIntrinsic = maxIntrinsic;
    }

    if (!minIntrinsic.isFinite || minIntrinsic < 0) minIntrinsic = 0;
    if (!maxIntrinsic.isFinite || maxIntrinsic < minIntrinsic)
      maxIntrinsic = minIntrinsic;

    double used;
    switch (style.width.type) {
      case CSSLengthType.MIN_CONTENT:
        used = minIntrinsic;
        break;
      case CSSLengthType.MAX_CONTENT:
        used = maxIntrinsic;
        break;
      case CSSLengthType.FIT_CONTENT:
        final double avail = available.isFinite ? available : maxIntrinsic;
        used = math.min(maxIntrinsic, math.max(minIntrinsic, avail));
        break;
      default:
        used = maxIntrinsic;
    }

    // Ensure the border-box can't be smaller than its own padding+border.
    used = math.max(used, minConstraintWidth);
    minConstraintWidth = used;
    maxConstraintWidth = used;
  }
  // Height should be not smaller than border and padding in vertical direction
  // when box-sizing is border-box which is only supported.
  double minConstraintHeight =
      styleBorder.vertical + stylePadding.vertical;
  double maxConstraintHeight =
      style.borderBoxLogicalHeight ?? double.infinity;

  // // Apply maxHeight constraint if specified
  // if (maxHeight != null && maxHeight < maxConstraintHeight) {
  //   maxConstraintHeight = maxHeight;
  // }

  if (parent is RenderFlexLayout) {
    double? flexBasis = style.flexBasis == CSSLengthValue.auto
        ? null
        : style.flexBasis?.computedValue;
    RenderBoxModel? parentRenderBoxModel = parent as RenderBoxModel?;
    // In flex layout, flex basis takes priority over width/height if set.
    // Flex-basis cannot be smaller than its content size which happens can not be known
    // in constraints apply stage, so flex-basis acts as min-width in constraints apply stage.
    if (flexBasis != null) {
      if (CSSFlex.isHorizontalFlexDirection(
          parentRenderBoxModel!.renderStyle.flexDirection)) {
        minWidth =
            minWidth != null ? math.max(flexBasis, minWidth) : flexBasis;
      } else {
        minHeight =
            minHeight != null ? math.max(flexBasis, minHeight) : flexBasis;
      }
    }
  }

  // Apply min/max width constraints for all display types
  if (minWidth != null && !isDisplayInline) {
    minConstraintWidth =
        minConstraintWidth < minWidth ? minWidth : minConstraintWidth;
    maxConstraintWidth =
        maxConstraintWidth < minWidth ? minWidth : maxConstraintWidth;
  }

  // Apply maxWidth constraint for all elements (including inline)
  if (maxWidth != null) {
    // Ensure maxConstraintWidth respects maxWidth, but don't reduce minConstraintWidth below border+padding
    maxConstraintWidth =
        maxConstraintWidth > maxWidth ? maxWidth : maxConstraintWidth;
    // Only reduce minConstraintWidth if maxWidth is larger than border+padding requirements
    double borderPadding =
        styleBorder.horizontal + stylePadding.horizontal;
    if (maxWidth >= borderPadding) {
      minConstraintWidth =
          minConstraintWidth > maxWidth ? maxWidth : minConstraintWidth;
    }
  }

  // Apply min/max height constraints when display is not inline
  if (!isDisplayInline) {
    if (minHeight != null) {
      minConstraintHeight =
          minConstraintHeight < minHeight ? minHeight : minConstraintHeight;
      maxConstraintHeight =
          maxConstraintHeight < minHeight ? minHeight : maxConstraintHeight;
    }
    if (maxHeight != null) {
      minConstraintHeight =
          minConstraintHeight > maxHeight ? maxHeight : minConstraintHeight;
      maxConstraintHeight =
          maxConstraintHeight > maxHeight ? maxHeight : maxConstraintHeight;
    }
  }

  // Normalize constraints to satisfy Flutter's requirement: min <= max.
  // This can happen when the computed minimum width from border/padding
  // exceeds the available max width from the parent (e.g., inline-block
  // with large horizontal padding inside a narrow container). In such
  // cases, cap the minimum to the maximum so constraints are valid.
  if (minConstraintWidth > maxConstraintWidth) {
    minConstraintWidth = maxConstraintWidth;
  }
  if (minConstraintHeight > maxConstraintHeight) {
    minConstraintHeight = maxConstraintHeight;
  }

  BoxConstraints constraints = BoxConstraints(
    minWidth: minConstraintWidth,
    maxWidth: maxConstraintWidth,
    minHeight: minConstraintHeight,
    maxHeight: maxConstraintHeight,
  );

  return constraints;
}