computedValue property

double get computedValue

Implementation

double get computedValue {
  if (calcValue != null) {
    _computedValue = calcValue!.computedValue(propertyName ?? '') ?? 0;
    return _computedValue!;
  }

  // Use cached value if type is not percentage which may needs 2 layout passes to resolve the
  // final computed value.
  if (renderStyle?.hasRenderBox() == true &&
      propertyName != null &&
      type != CSSLengthType.PERCENTAGE) {
    double? cachedValue = getCachedComputedValue(renderStyle!, propertyName!);
    if (cachedValue != null) {
      return cachedValue;
    }
  }

  final realPropertyName = propertyName?.split('_').first ?? propertyName;
  switch (type) {
    case CSSLengthType.PX:
      _computedValue = value;
      break;
    case CSSLengthType.RPX:
      FlutterView window = renderStyle!.currentFlutterView;
      _computedValue = value! / 750.0 * window.physicalSize.width / window.devicePixelRatio;
      break;
    case CSSLengthType.EM:
      // Font size of the parent, in the case of typographical properties like font-size,
      // and font size of the element itself, in the case of other properties like width.
      if (realPropertyName == FONT_SIZE) {
        // If root element set fontSize as em unit.
        if (renderStyle!.getParentRenderStyle() == null) {
          _computedValue = value! * 16;
        } else {
          _computedValue = value! * renderStyle!.getParentRenderStyle()!.fontSize.computedValue;
        }
      } else {
        _computedValue = value! * renderStyle!.fontSize.computedValue;
      }
      break;
    case CSSLengthType.EX:
      // Approximate 1ex via a 0.5em fallback. For font-size itself, resolve
      // against the parent font size to avoid recursion (per CSS: em/ex in
      // font-size are relative to the inherited font-size).
      double baseEmPx;
      if (realPropertyName == FONT_SIZE) {
        if (renderStyle!.getParentRenderStyle() == null) {
          baseEmPx = 16; // default root font size baseline
        } else {
          baseEmPx = renderStyle!.getParentRenderStyle()!.fontSize.computedValue;
        }
      } else {
        baseEmPx = renderStyle!.fontSize.computedValue;
      }
      _computedValue = value! * (baseEmPx * _exToEmFallbackRatio);
      break;
    case CSSLengthType.REM:
      // If root element set fontSize as rem unit.
      if (renderStyle!.getParentRenderStyle() == null) {
        _computedValue = value! * 16;
      } else {
        // Font rem is calculated against the root element's font size.
        _computedValue = value! * renderStyle!.rootFontSize;
      }
      break;
    case CSSLengthType.VH:
      _computedValue = value! * (renderStyle!.getCurrentViewportBox()?.boxSize ?? renderStyle!.viewportSize).height;
      break;
    case CSSLengthType.VW:
      _computedValue = value! * (renderStyle!.getCurrentViewportBox()?.boxSize ?? renderStyle!.viewportSize).width;
      break;
    // 1% of viewport's smaller (vw or vh) dimension.
    // If the height of the viewport is less than its width, 1vmin will be equivalent to 1vh.
    // If the width of the viewport is less than it’s height, 1vmin is equvialent to 1vw.
    case CSSLengthType.VMIN:
      _computedValue = value! * renderStyle!.viewportSize.shortestSide;
      break;
    case CSSLengthType.VMAX:
      _computedValue = value! * renderStyle!.viewportSize.longestSide;
      break;
    case CSSLengthType.PERCENTAGE:
      CSSPositionType positionType = renderStyle!.position;
      bool isPositioned = positionType == CSSPositionType.absolute || positionType == CSSPositionType.fixed;
      RenderStyle? currentRenderStyle = renderStyle;

      RenderStyle? parentRenderStyle = isPositioned
          ? currentRenderStyle?.target.getContainingBlockElement()?.renderStyle
          : currentRenderStyle?.getParentRenderStyle();

      // Should access the renderStyle of renderBoxModel parent but not renderStyle parent
      // cause the element of renderStyle parent may not equal to containing block.
      // RenderObject? containerRenderBox = renderBoxModel?.parent;
      // CSSRenderStyle? parentRenderStyle;
      while (parentRenderStyle != null) {
        if (parentRenderStyle.isBoxModel() && (_isPercentageRelativeContainerRenderStyle(parentRenderStyle))) {
          break;
        }
        parentRenderStyle = parentRenderStyle.getParentRenderStyle();
      }

      RenderWidgetElementChild? renderWidgetElementChild =
          currentRenderStyle?.target.attachedRenderer?.findWidgetElementChild();
      bool shouldInheritRenderWidgetElementConstraintsWidth =
          parentRenderStyle?.isSelfRenderWidget() == true && renderWidgetElementChild != null;
      double? parentWidgetConstraintWidth;
      bool shouldInheritRenderWidgetElementConstraintsHeight = false;
      double? parentWidgetConstraintHeight;
      try {
        parentWidgetConstraintWidth = renderWidgetElementChild?.constraints.maxWidth;
        shouldInheritRenderWidgetElementConstraintsHeight = parentRenderStyle?.isSelfRenderWidget() == true &&
            renderWidgetElementChild != null &&
            renderWidgetElementChild.constraints.maxHeight.isFinite &&
            renderWidgetElementChild.constraints.maxHeight !=
                currentRenderStyle!.target.ownerView.viewport!.boxSize!.height;
        parentWidgetConstraintHeight = renderWidgetElementChild?.constraints.maxHeight;
      } catch (_) {}

      // Percentage relative width priority: RenderWidgetChild's constraints > logical width > renderer width
      double? parentPaddingBoxWidth = parentRenderStyle?.paddingBoxLogicalWidth ?? parentRenderStyle?.paddingBoxWidth;
      double? parentContentBoxWidth = parentRenderStyle?.contentBoxLogicalWidth ?? parentRenderStyle?.contentBoxWidth;

      // Override the contentBoxWidth
      if (shouldInheritRenderWidgetElementConstraintsWidth) {
        parentContentBoxWidth = parentWidgetConstraintWidth;
      }

      // Percentage relative height priority: logical height > renderer height.
      // For positioned elements, allow resolving against the parent's rendered
      // padding-box height even when the parent is in a relayout pass; this
      // mirrors the horizontal behavior and ensures overlays like
      // `top:0; height:100%` can track auto-height containers.
      double? parentPaddingBoxHeight = parentRenderStyle?.paddingBoxLogicalHeight;
      if (parentPaddingBoxHeight == null) {
        if (isPositioned) {
          parentPaddingBoxHeight = parentRenderStyle?.paddingBoxHeight;
        } else if (parentRenderStyle?.isSelfNeedsRelayout() != true) {
          parentPaddingBoxHeight = parentRenderStyle?.paddingBoxHeight;
        }
      }
      double? parentContentBoxHeight = parentRenderStyle?.contentBoxLogicalHeight ??
          (parentRenderStyle?.isSelfNeedsRelayout() == true ? null : parentRenderStyle?.contentBoxHeight);
      double? parentContentBoxLogicalHeight = parentRenderStyle?.contentBoxLogicalHeight;

      if (shouldInheritRenderWidgetElementConstraintsHeight) {
        parentContentBoxHeight = parentWidgetConstraintHeight;
        parentContentBoxLogicalHeight = parentWidgetConstraintHeight;
      }

      // Positioned element is positioned relative to the padding box of its containing block
      // while the others relative to the content box.
      double? relativeParentWidth = isPositioned ? parentPaddingBoxWidth : parentContentBoxWidth;

      switch (realPropertyName) {
        case FONT_SIZE:
          // Relative to the parent font size.
          if (renderStyle!.getParentRenderStyle() == null) {
            _computedValue = value! * 16;
          } else {
            _computedValue = value! * renderStyle!.getParentRenderStyle()!.fontSize.computedValue;
          }
          break;
        case TEXT_INDENT:
          // Percentages for text-indent refer to the width of the containing block.
          // Use the parent content box width for in-flow elements.
          if (relativeParentWidth != null && relativeParentWidth.isFinite) {
            _computedValue = value! * relativeParentWidth;
          } else if (parentRenderStyle != null) {
            double? cbLogicalW = parentRenderStyle.contentBoxLogicalWidth;
            if (cbLogicalW != null && cbLogicalW.isFinite) {
              _computedValue = value! * cbLogicalW;
            } else {
              // Fallback to constraints or viewport width if needed.
              final rbox = parentRenderStyle.attachedRenderBoxModel;
              if (rbox != null && rbox.hasSize && rbox.constraints.maxWidth.isFinite) {
                _computedValue = value! * rbox.constraints.maxWidth;
              } else {
                _computedValue = value! * renderStyle!.viewportSize.width;
              }
            }
          } else {
            // Root-level: resolve against viewport.
            _computedValue = value! * renderStyle!.viewportSize.width;
          }
          break;
        case LINE_HEIGHT:
          // Relative to the font size of the element itself.
          _computedValue = value! * renderStyle!.fontSize.computedValue;
          break;
        case WIDTH:
        case MIN_WIDTH:
        case MAX_WIDTH:
          // Only resolve percent widths when the containing block has a definite inline size.
          if (!isPositioned && parentRenderStyle != null && !_hasDefiniteInlineSize(parentRenderStyle)) {
            _computedValue = double.infinity;
            break;
          }
          // For inline-block (or inline-flex) with width:auto (shrink-to-fit), the containing block
          // inline size is not yet definite for in-flow children. Per CSS, percentage widths here
          // compute to auto. However, once the inline-block’s shrink-to-fit content width becomes
          // definite, percentage widths of descendants MUST resolve against that width.
          if (!isPositioned &&
              parentRenderStyle != null &&
              (parentRenderStyle.effectiveDisplay == CSSDisplay.inlineBlock ||
                  parentRenderStyle.effectiveDisplay == CSSDisplay.inlineFlex) &&
              parentRenderStyle.width.isAuto) {
            // Treat as indefinite only if the parent has no definite logical content width yet.
            final double? cbLogicalW = (parentRenderStyle is CSSRenderStyle)
                ? (parentRenderStyle as CSSRenderStyle).contentBoxLogicalWidth
                : parentRenderStyle.contentBoxLogicalWidth;
            if (cbLogicalW == null) {
              _computedValue = double.infinity;
              break;
            }
          }
          if (relativeParentWidth != null) {
            _computedValue = value! * relativeParentWidth;
            // Do not upscale replaced elements inside shrink-to-fit inline-block containers.
            if (!isPositioned &&
                parentRenderStyle != null &&
                parentRenderStyle.effectiveDisplay == CSSDisplay.inlineBlock &&
                parentRenderStyle.width.isAuto &&
                renderStyle != null &&
                renderStyle!.isSelfRenderReplaced() &&
                _computedValue != null) {
              final double iw = renderStyle!.intrinsicWidth;
              if (iw > 0 && _computedValue! > iw) {
                _computedValue = iw;
              }
            }
          } else {
            // Attempt to force parent width computation before giving up
            if (parentRenderStyle != null && parentRenderStyle is CSSRenderStyle) {
              // Ensure parent's layout width is computed
              (parentRenderStyle as CSSRenderStyle).computeContentBoxLogicalWidth();
              // Try to get parent width again after computation
              double? recomputedParentWidth =
                  isPositioned ? parentRenderStyle.paddingBoxLogicalWidth : parentRenderStyle.contentBoxLogicalWidth;

                if (recomputedParentWidth != null) {
                  _computedValue = value! * recomputedParentWidth;
                if (!isPositioned &&
                    parentRenderStyle.effectiveDisplay == CSSDisplay.inlineBlock &&
                    parentRenderStyle.width.isAuto &&
                    renderStyle != null &&
                    renderStyle!.isSelfRenderReplaced() &&
                    _computedValue != null) {
                  final double iw = renderStyle!.intrinsicWidth;
                    if (iw > 0 && _computedValue! > iw) {
                      _computedValue = iw;
                    }
                  }
                } else {
                // Last resort: use available constraint width or mark for relayout
                RenderBox? parentRenderBox = parentRenderStyle.attachedRenderBoxModel;
                if (parentRenderBox != null && parentRenderBox.hasSize) {
                  double constraintWidth = parentRenderBox.constraints.maxWidth;
                  if (constraintWidth != double.infinity) {
                    _computedValue = value! * constraintWidth;
                    if (!isPositioned &&
                        parentRenderStyle.effectiveDisplay == CSSDisplay.inlineBlock &&
                        parentRenderStyle.width.isAuto &&
                        renderStyle != null &&
                        renderStyle!.isSelfRenderReplaced() &&
                        _computedValue != null) {
                      final double iw = renderStyle!.intrinsicWidth;
                      if (iw > 0 && _computedValue! > iw) {
                        _computedValue = iw;
                      }
                    }
                  } else {
                    // Avoid spurious relayout loops for shrink-to-fit inline-block cycles
                    renderStyle?.markParentNeedsRelayout();
                    _computedValue = double.infinity;
                  }
                } else {
                  // Parent not laid out yet; mark once and return auto for now
                  renderStyle?.markParentNeedsRelayout();
                  _computedValue = double.infinity;
                }
              }
            } else {
              _computedValue = double.infinity;
            }
          }
          break;
        case HEIGHT:
        case MIN_HEIGHT:
        case MAX_HEIGHT:
          // The percentage of height is calculated with respect to the height of the generated box's containing block.
          // If the height of the containing block is not specified explicitly (i.e., it depends on content height),
          // and this element is not absolutely positioned, the value computes to 'auto'.
          // https://www.w3.org/TR/CSS2/visudet.html#propdef-height
          // There are two exceptions when percentage height is resolved against actual render height of parent:
          // 1. positioned element
          // 2. parent is flex item
          RenderStyle? grandParentRenderStyle = parentRenderStyle?.getParentRenderStyle();
          bool isGrandParentFlexLayout = grandParentRenderStyle?.display == CSSDisplay.flex ||
              grandParentRenderStyle?.display == CSSDisplay.inlineFlex;

          // The percentage height of positioned element and flex item resolves against the rendered height
          // of parent, mark parent as needs relayout if rendered height is not ready yet.
          if (isPositioned || isGrandParentFlexLayout) {
            double? relativeParentHeight = isPositioned ? parentPaddingBoxHeight : parentContentBoxHeight;
            if (relativeParentHeight != null) {
              _computedValue = value! * relativeParentHeight;
            } else {
              // Mark parent to relayout to get renderer height of parent.
              renderStyle?.markParentNeedsRelayout();
              _computedValue = double.infinity;
            }
          } else {
            double? relativeParentHeight = parentContentBoxLogicalHeight;
            if (relativeParentHeight != null) {
              _computedValue = value! * relativeParentHeight;
            } else {
              // Resolves height as auto if parent has no height specified.
              _computedValue = double.infinity;
            }
          }
          break;
        case PADDING_TOP:
        case PADDING_RIGHT:
        case PADDING_BOTTOM:
        case PADDING_LEFT:
        case MARGIN_LEFT:
        case MARGIN_RIGHT:
        case MARGIN_TOP:
        case MARGIN_BOTTOM:
          // https://www.w3.org/TR/css-box-3/#padding-physical
          // Percentage refer to logical width of containing block
          if (relativeParentWidth != null) {
            _computedValue = value! * relativeParentWidth;
          } else {
            // Mark parent to relayout to get renderer height of parent.
            renderStyle?.markParentNeedsRelayout();
            _computedValue = 0;
          }
          break;
        case FLEX_BASIS:
          // Flex-basis computation is called in RenderFlexLayout which
          // will ensure parent exists.
          RenderStyle? parentRenderStyle = renderStyle!.getParentRenderStyle();
          if (parentRenderStyle == null) {
            _computedValue = 0;
            break;
          }
          double? mainContentSize =
              parentRenderStyle!.flexDirection == FlexDirection.row ? parentContentBoxWidth : parentContentBoxHeight;
          if (mainContentSize != null) {
            _computedValue = mainContentSize * value!;
          } else {
            // Resolves as 0 when parent's inner main size is not specified.
            _computedValue = 0;
          }
          // Refer to the flex container's inner main size.
          break;

        // https://www.w3.org/TR/css-position-3/#valdef-top-percentage
        // The inset is a percentage relative to the containing block’s size in the corresponding
        // axis (e.g. width for left or right, height for top and bottom). For sticky positioned boxes,
        // the inset is instead relative to the relevant scrollport’s size. Negative values are allowed.
        case TOP:
        case BOTTOM:
          // Offset of positioned element starts from the edge of padding box of containing block.
          if (parentPaddingBoxHeight != null) {
            _computedValue = value! * parentPaddingBoxHeight;
          } else {
            // Mark parent to relayout to get renderer height of parent.
            renderStyle?.markParentNeedsRelayout();
            // Set as initial value, use infinity as auto value.
            _computedValue = double.infinity;
          }
          break;
        case LEFT:
        case RIGHT:
          // Offset of positioned element starts from the edge of padding box of containing block.
          if (parentPaddingBoxWidth != null) {
            _computedValue = value! * parentPaddingBoxWidth;
          } else {
            // Mark parent to relayout to get renderer height of parent.
            renderStyle?.markParentNeedsRelayout();
            _computedValue = double.infinity;
          }
          break;

        case TRANSLATE:
        case BACKGROUND_SIZE:
        case BORDER_TOP_LEFT_RADIUS:
        case BORDER_TOP_RIGHT_RADIUS:
        case BORDER_BOTTOM_LEFT_RADIUS:
        case BORDER_BOTTOM_RIGHT_RADIUS:
          // Percentages for the horizontal axis refer to the width of the box.
          // Percentages for the vertical axis refer to the height of the box.
          // Prefer actual laid-out border box size in order to stay consistent with
          // positioned offset calculations performed during layout. Fallback to
          // logical sizes when layout size is not yet available (e.g., pre-layout).
          double? borderBoxWidth = renderStyle!.borderBoxWidth ?? renderStyle!.borderBoxLogicalWidth;
          double? borderBoxHeight = renderStyle!.borderBoxHeight ?? renderStyle!.borderBoxLogicalHeight;
          double? borderBoxDimension = axisType == Axis.horizontal ? borderBoxWidth : borderBoxHeight;

            if (borderBoxDimension != null) {
              _computedValue = value! * borderBoxDimension;
            } else {
              _computedValue = propertyName == TRANSLATE
                  // Transform will be cached once resolved, so avoid resolve if width not defined.
                  // Use double.infinity to indicate percentage not resolved.
                  ? double.infinity
                  : 0;

          }
          break;
        case BACKGROUND_POSITION_X:
          double? borderBoxWidth = renderStyle!.borderBoxWidth ?? renderStyle!.borderBoxLogicalWidth;
          if (isPercentage && borderBoxWidth != null) {
            final destinationWidth = renderStyle?.getRenderBoxValueByType(RenderObjectGetType.self,
                    (renderBox, _) => renderBox.boxPainter?.backgroundImageSize?.width.toDouble()) ??
                0;
            _computedValue = (borderBoxWidth - destinationWidth) * value!;
            if (DebugFlags.enableBackgroundLogs) {
              renderingLogger.finer('[Background] resolve BACKGROUND_POSITION_X: containerW=' +
                  '${borderBoxWidth.toStringAsFixed(2)} destW=${destinationWidth.toStringAsFixed(2)} pct=${(value! * 100).toStringAsFixed(1)}% -> ${_computedValue!.toStringAsFixed(2)}');
            }
          } else {
            _computedValue = value!;
          }
          break;
        case BACKGROUND_POSITION_Y:
          double? borderBoxHeight = renderStyle!.borderBoxHeight ?? renderStyle!.borderBoxLogicalHeight;
          if (isPercentage && borderBoxHeight != null) {
            final destinationHeight = renderStyle?.getRenderBoxValueByType(RenderObjectGetType.self,
                    (renderBox, _) => renderBox.boxPainter?.backgroundImageSize?.height.toDouble()) ??
                0;
            _computedValue = (borderBoxHeight - destinationHeight) * value!;
            if (DebugFlags.enableBackgroundLogs) {
              renderingLogger.finer('[Background] resolve BACKGROUND_POSITION_Y: containerH=' +
                  '${borderBoxHeight.toStringAsFixed(2)} destH=${destinationHeight.toStringAsFixed(2)} pct=${(value! * 100).toStringAsFixed(1)}% -> ${_computedValue!.toStringAsFixed(2)}');
            }
          } else {
            _computedValue = value!;
          }
          break;

        case GAP:
        case ROW_GAP:
        case COLUMN_GAP:
          // Gap percentages resolve against the content area of the container itself
          // For row-gap: against the container's height
          // For column-gap: against the container's width
          // For gap (shorthand): against width for horizontal, height for vertical
          double? containerContentWidth = renderStyle!.contentBoxWidth ?? renderStyle!.contentBoxLogicalWidth;
          double? containerContentHeight = renderStyle!.contentBoxHeight ?? renderStyle!.contentBoxLogicalHeight;

          if (realPropertyName == ROW_GAP) {
            // Row gap resolves against container height
            if (containerContentHeight != null && containerContentHeight > 0) {
              _computedValue = value! * containerContentHeight;
            } else {
              _computedValue = 0;
            }
          } else if (realPropertyName == COLUMN_GAP) {
            // Column gap resolves against container width
            if (containerContentWidth != null && containerContentWidth > 0) {
              _computedValue = value! * containerContentWidth;
            } else {
              _computedValue = 0;
            }
          } else {
            // GAP shorthand - resolve against width (like column-gap for now)
            if (containerContentWidth != null && containerContentWidth > 0) {
              _computedValue = value! * containerContentWidth;
            } else {
              _computedValue = 0;
            }
          }
          break;
      }
      break;
    case CSSLengthType.CONTENT:
      // The 'content' keyword is only valid for certain properties like flex-basis.
      // Defer resolution to the layout algorithm; treat as 0 length here so callers
      // that incorrectly query computedValue don't crash. Layout code must special-case.
      _computedValue = 0;
      break;
    default:
      // @FIXME: Type AUTO not always resolves to 0, in cases such as `margin: auto`, `width: auto`.
      _computedValue = 0;
  }

  // Ensure _computedValue is never null
  _computedValue ??= 0;

  // Cache computed value.
  if (renderStyle?.hasRenderBox() == true && propertyName != null && type != CSSLengthType.PERCENTAGE) {
    cacheComputedValue(renderStyle!, propertyName!, _computedValue!);
  }
  return _computedValue!;
}