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?.renderBoxModel != null && propertyName != null && type != CSSLengthType.PERCENTAGE) {
    RenderBoxModel? renderBoxModel = renderStyle!.renderBoxModel;
    double? cachedValue = getCachedComputedValue(renderBoxModel.hashCode, propertyName!);
    if (cachedValue != null) {
      return cachedValue;
    }
  }

  final realPropertyName = propertyName?.split('_').first ?? propertyName;
  switch (type) {
    case CSSLengthType.PX:
      _computedValue = value;
      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!.parent == null) {
          _computedValue = value! * 16;
        } else {
          _computedValue = value! * renderStyle!.parent!.fontSize.computedValue;
        }
      } else {
        _computedValue = value! * renderStyle!.fontSize.computedValue;
      }
      break;
    case CSSLengthType.REM:
      // If root element set fontSize as rem unit.
      if (renderStyle!.parent == 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!.viewportSize.height;
      break;
    case CSSLengthType.VW:
      _computedValue = value! * 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;

      RenderBoxModel? renderBoxModel = renderStyle!.renderBoxModel;
      // 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 (containerRenderBox != null) {
        if (containerRenderBox is RenderBoxModel && (_isPercentageRelativeContainer(containerRenderBox))) {
          // Get the renderStyle of outer scrolling box cause the renderStyle of scrolling
          // content box is only a fraction of the complete renderStyle.
          parentRenderStyle = containerRenderBox.isScrollingContentBox
              ? (containerRenderBox.parent as RenderBoxModel).renderStyle
              : containerRenderBox.renderStyle;
          break;
        }
        containerRenderBox = containerRenderBox.parent;
      }

      // Percentage relative width priority: logical width > renderer width
      double? parentPaddingBoxWidth = parentRenderStyle?.paddingBoxLogicalWidth ?? parentRenderStyle?.paddingBoxWidth;
      double? parentContentBoxWidth = parentRenderStyle?.contentBoxLogicalWidth ?? parentRenderStyle?.contentBoxWidth;
      // Percentage relative height priority: logical height > renderer height
      double? parentPaddingBoxHeight =
          parentRenderStyle?.paddingBoxLogicalHeight ?? parentRenderStyle?.paddingBoxHeight;
      double? parentContentBoxHeight =
          parentRenderStyle?.contentBoxLogicalHeight ?? parentRenderStyle?.contentBoxHeight;

      // 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;
      double? relativeParentHeight = isPositioned ? parentPaddingBoxHeight : parentContentBoxHeight;

      switch (realPropertyName) {
        case FONT_SIZE:
          // Relative to the parent font size.
          if (renderStyle!.parent == null) {
            _computedValue = value! * 16;
          } else {
            _computedValue = value! * renderStyle!.parent!.fontSize.computedValue;
          }
          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:
          if (relativeParentWidth != null) {
            _computedValue = value! * relativeParentWidth;
          } else {
            // Mark parent to relayout to get renderer width of parent.
            if (renderBoxModel != null) {
              renderBoxModel.markParentNeedsRelayout();
            }
            _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?.parent;
          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) {
            if (relativeParentHeight != null) {
              _computedValue = value! * relativeParentHeight;
            } else {
              // Mark parent to relayout to get renderer height of parent.
              if (renderBoxModel != null) {
                renderBoxModel.markParentNeedsRelayout();
              }
              _computedValue = double.infinity;
            }
          } else {
            double? relativeParentHeight = parentRenderStyle?.contentBoxLogicalHeight;
            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.
            if (renderBoxModel != null) {
              renderBoxModel.markParentNeedsRelayout();
            }
            _computedValue = 0;
          }
          break;
        case FLEX_BASIS:
          // Flex-basis computation is called in RenderFlexLayout which
          // will ensure parent exists.
          RenderStyle parentRenderStyle = renderStyle!.parent!;
          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.
            if (renderBoxModel != null) {
              renderBoxModel.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.
            if (renderBoxModel != null) {
              renderBoxModel.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.
          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 = renderBoxModel!.boxPainter?.backgroundImageSize?.width.toDouble() ?? 0;
            _computedValue = (borderBoxWidth - destinationWidth) * value!;
          } else {
            _computedValue = value!;
          }
          break;
        case BACKGROUND_POSITION_Y:
          double? borderBoxHeight = renderStyle!.borderBoxHeight ?? renderStyle!.borderBoxLogicalHeight;
          if (isPercentage && borderBoxHeight != null) {
            final destinationHeight = renderBoxModel!.boxPainter?.backgroundImageSize?.height.toDouble() ?? 0;
            _computedValue = (borderBoxHeight - destinationHeight) * value!;
          } else {
            _computedValue = value!;
          }
          break;

        case RX:
          final target = renderStyle!.target;
          if (target is SVGElement) {
            final viewBox = target.findRoot()?.viewBox;
            if (viewBox != null) {
              _computedValue = viewBox.width * value!;
            }
          }
          break;

        case RY:
          final target = renderStyle!.target;
          if (target is SVGElement) {
            final viewBox = target.findRoot()?.viewBox;
            if (viewBox != null) {
              _computedValue = viewBox.height * value!;
            }
          }
          break;
      }
      break;
    default:
      // @FIXME: Type AUTO not always resolves to 0, in cases such as `margin: auto`, `width: auto`.
      _computedValue = 0;
  }

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