/*
 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
 * Copyright (C) 2003-2026 Apple Inc. All rights reserved.
 * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
 * Copyright (C) 2010 Daniel Bates (dbates@intudata.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "RenderListMarker.h"

#include "CSSCounterStyleDescriptors.h"
#include "CSSCounterStyleRegistry.h"
#include "CSSFontSelector.h"
#include "Document.h"
#include "DocumentInlines.h"
#include "FontCascade.h"
#include "FontCascadeInlines.h"
#include "FontCascadeDescription.h"
#include "GraphicsContext.h"
#include "PaintInfoInlines.h"
#include "RenderBlockFlow.h"
#include "RenderBlockInlines.h"
#include "RenderBoxInlines.h"
#include "RenderLayer.h"
#include "RenderListItem.h"
#include "RenderMultiColumnFlow.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderObjectInlines.h"
#include "RenderView.h"
#include "Settings.h"
#include "StyleComputedStyle+GettersInlines.h"
#include "StyleComputedStyle+SettersInlines.h"
#include "StyleContent.h"
#include "StyleListStyleType.h"
#include "StyleScope.h"
#include "TextUtil.h"
#include <wtf/StackStats.h>
#include <wtf/TZoneMallocInlines.h>
#include <wtf/text/MakeString.h>
#include <wtf/unicode/CharacterNames.h>

namespace WebCore {

WTF_MAKE_TZONE_ALLOCATED_IMPL(RenderListMarker);

RenderListMarker::RenderListMarker(RenderListItem& listItem, Style::ComputedStyle&& style)
    : RenderBox(Type::ListMarker, listItem.document(), WTF::move(style))
    , m_listItem(listItem)
{
    setInline(true);
    setBlockLevelReplacedOrAtomicInline(true); // pretend to be replaced
    ASSERT(isRenderListMarker());
}

// Do not add any code in below destructor. Add it to willBeDestroyed() instead.
RenderListMarker::~RenderListMarker() = default;

void RenderListMarker::willBeDestroyed()
{
    if (m_image)
        protect(m_image)->removeClient(*this);
    RenderBox::willBeDestroyed();
}

static Style::Difference NODELETE adjustedStyleDifference(Style::Difference diff, const Style::ComputedStyle& oldStyle, const Style::ComputedStyle& newStyle)
{
    if (diff >= Style::DifferenceResult::Layout)
        return diff;
    // FIXME: Preferably we do this at Style::ComputedStyle::changeRequiresLayout but checking against pseudo(::marker) is not sufficient.
    auto needsLayout =
           oldStyle.listStylePosition() != newStyle.listStylePosition()
        || oldStyle.listStyleType() != newStyle.listStyleType()
        || oldStyle.display().isInlineType() != newStyle.display().isInlineType();
    return needsLayout ? Style::DifferenceResult::Layout : diff;
}

void RenderListMarker::styleWillChange(Style::Difference diff, const Style::ComputedStyle& newStyle)
{
    RenderBox::styleWillChange(adjustedStyleDifference(diff, style(), newStyle), newStyle);
}

void RenderListMarker::styleDidChange(Style::Difference diff, const Style::ComputedStyle* oldStyle)
{
    if (oldStyle)
        diff = adjustedStyleDifference(diff, *oldStyle, style());
    RenderBox::styleDidChange(diff, oldStyle);

    if (RefPtr newImage = style().listStyleImage().tryStyleImage(); m_image != newImage) {
        if (m_image)
            protect(m_image)->removeClient(*this);
        m_image = WTF::move(newImage);
        if (m_image)
            protect(m_image)->addClient(*this);
    }
}

bool RenderListMarker::isImage() const
{
    // `content` supersedes list-style-image (css-lists-3 §3.3), so a marker with generated content
    // is never treated as an image marker (affects inline margins, baseline, and layout attributes).
    return m_image && !protect(m_image)->errorOccurred() && !hasContent();
}

bool RenderListMarker::hasContent() const
{
    return document().settings().cssMarkerContentEnabled() && style().content().isData();
}

RenderBlockFlow* RenderListMarker::contentContainer() const
{
    // When the marker has generated content, its sole child is the anonymous
    // inline-block box holding that content (created by RenderTreeBuilder::List).
    return dynamicDowncast<RenderBlockFlow>(firstChild());
}

LayoutRect RenderListMarker::localSelectionRect()
{
    return LayoutRect(LayoutPoint(), borderBoxSize());
}

static String reversed(StringView string)
{
    auto length = string.length();
    if (length <= 1)
        return string.toString();
    std::span<char16_t> characters;
    auto result = String::createUninitialized(length, characters);
    for (unsigned i = 0; i < length; ++i)
        characters[i] = string[length - i - 1];
    return result;
}

struct TextRunWithUnderlyingString {
    TextRun textRun;
    String underlyingString;
    operator const TextRun&() const { return textRun; }
};

static FontCascade disclosureMarkerFontCascade(const Style::ComputedStyle& style, Document& document)
{
    auto fontDescription = FontCascadeDescription { style.fontDescription() };
    fontDescription.setFamilies({ { "system-ui"_s, FontFamilyKind::Generic } });
    auto fontCascade = FontCascade(WTF::move(fontDescription));
    fontCascade.update(&document.fontSelector());
    return fontCascade;
}

static auto textRunForContent(ListMarkerTextContent textContent, const Style::ComputedStyle& style) -> TextRunWithUnderlyingString
{
    ASSERT(!textContent.isEmpty());

    // Since the bidi algorithm doesn't run on this text, we instead reorder the characters here.
    // We use u_charDirection to figure out if the marker text is RTL and assume the suffix matches the surrounding direction.
    String textForRun;
    if (textContent.textDirection == TextDirection::LTR) {
        if (style.writingMode().isBidiLTR())
            textForRun = textContent.textWithSuffix;
        else
            textForRun = makeString(reversed(textContent.suffix()), textContent.textWithoutSuffix());
    } else {
        if (!style.writingMode().isBidiLTR())
            textForRun = reversed(textContent.textWithSuffix);
        else
            textForRun = makeString(reversed(textContent.textWithoutSuffix()), textContent.suffix());
    }
    auto textRun = RenderBlock::constructTextRun(textForRun, style);
    return { WTF::move(textRun), WTF::move(textForRun) };
}

void RenderListMarker::paintDisclosureMarker(GraphicsContext& context, const FloatRect& markerRect)
{
    auto systemUIFontCascade = disclosureMarkerFontCascade(style(), protect(document()));
    auto textOrigin = FloatPoint { markerRect.x(), markerRect.y() + systemUIFontCascade.metricsOfPrimaryFont().ascent() };
    textOrigin = roundPointToDevicePixels(LayoutPoint(textOrigin), protect(document())->deviceScaleFactor(), writingMode().isLogicalLeftInlineStart());
    context.drawText(systemUIFontCascade, textRunForContent(m_textContent, style()), textOrigin);
}

void RenderListMarker::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    if (style().usedVisibility() != Visibility::Visible)
        return;

    LayoutPoint boxOrigin(paintOffset + location());
    LayoutRect overflowRect(visualOverflowRect());
    overflowRect.moveBy(boxOrigin);
    if (!paintInfo.rect.intersects(overflowRect))
        return;

    if (CheckedPtr container = contentContainer()) {
        if (paintInfo.phase == PaintPhase::Accessibility) {
            paintInfo.accessibilityRegionContext()->takeBounds(*this, LayoutRect(boxOrigin, borderBoxSize()));
            return;
        }

        if (paintInfo.phase == PaintPhase::Foreground && selectionState() != HighlightState::None) {
            LayoutRect selectionRect = localSelectionRect();
            selectionRect.moveBy(boxOrigin);
            paintInfo.context().fillRect(snappedIntRect(selectionRect), m_listItem->selectionBackgroundColor());
        }

        // Paint the generated-content subtree as an atomic inline: paintAsInlineBlock fans out all
        // sub-phases for Foreground and forwards Selection/EventRegion/TextClip to the child's paint().
        container->paintAsInlineBlock(paintInfo, boxOrigin);
        return;
    }

    // The bespoke bullet/text/image painting below only applies to the foreground and accessibility phases.
    if (paintInfo.phase != PaintPhase::Foreground && paintInfo.phase != PaintPhase::Accessibility)
        return;

    LayoutRect box(boxOrigin, borderBoxSize());

    auto markerRect = relativeMarkerRect();
    markerRect.moveBy(boxOrigin);

    if (paintInfo.phase == PaintPhase::Accessibility) {
        paintInfo.accessibilityRegionContext()->takeBounds(*this, markerRect);
        return;
    }

    if (markerRect.isEmpty())
        return;

    GraphicsContext& context = paintInfo.context();

    if (isImage()) {
        if (RefPtr markerImage = protect(m_image)->image(this, markerRect.size(), context))
            context.drawImage(*markerImage, markerRect, { imageOrientation() });
        if (selectionState() != HighlightState::None) {
            LayoutRect selectionRect = localSelectionRect();
            selectionRect.moveBy(boxOrigin);
            context.fillRect(snappedIntRect(selectionRect), m_listItem->selectionBackgroundColor());
        }
        return;
    }

    if (selectionState() != HighlightState::None) {
        LayoutRect selectionRect = localSelectionRect();
        selectionRect.moveBy(boxOrigin);
        context.fillRect(snappedIntRect(selectionRect), m_listItem->selectionBackgroundColor());
    }

    auto color = style().visitedDependentTextFillColorApplyingColorFilter();
    context.setStrokeColor(color);
    context.setStrokeStyle(StrokeStyle::SolidStroke);
    context.setStrokeThickness(1.0f);
    context.setFillColor(color);

    auto listStyleType = style().listStyleType();
    if (listStyleType.isDisc()) {
        context.fillEllipse(markerRect);
        return;
    }
    if (listStyleType.isCircle()) {
        context.strokeEllipse(markerRect);
        return;
    }
    if (listStyleType.isSquare()) {
        context.fillRect(markerRect);
        return;
    }

    if (m_textContent.isEmpty())
        return;

    GraphicsContextStateSaver stateSaver(context, false);
    if (!writingMode().isHorizontal()) {
        markerRect.moveBy(-boxOrigin);
        markerRect = markerRect.transposedRect();
        markerRect.moveBy(FloatPoint(box.x(), box.y() - logicalHeight()));
        stateSaver.save();
        context.translate(markerRect.x(), markerRect.maxY());
        context.rotate(static_cast<float>(deg2rad(90.)));
        context.translate(-markerRect.x(), -markerRect.maxY());
    }

    if (isDisclosureMarker()) {
        paintDisclosureMarker(context, markerRect);
        return;
    }

    auto textOrigin = FloatPoint { markerRect.x(), markerRect.y() + style().fontCascade().metricsOfPrimaryFont().ascent() };
    textOrigin = roundPointToDevicePixels(LayoutPoint(textOrigin), protect(document())->deviceScaleFactor(), writingMode().isLogicalLeftInlineStart());
    context.drawText(style().fontCascade(), textRunForContent(m_textContent, style()), textOrigin);
}

RenderBox* RenderListMarker::parentBox(RenderBox& box)
{
    ASSERT(m_listItem);
    CheckedPtr multiColumnFlow = dynamicDowncast<RenderMultiColumnFlow>(m_listItem->enclosingFragmentedFlow());
    if (!multiColumnFlow)
        return box.parentBox();
    auto* placeholder = multiColumnFlow->findColumnSpannerPlaceholder(box);
    return placeholder ? placeholder->parentBox() : box.parentBox();
};

void RenderListMarker::layout()
{
    StackStats::LayoutCheckPoint layoutCheckPoint;
    ASSERT(needsLayout());

    LayoutUnit blockOffset;
    for (auto* ancestor = parentBox(*this); ancestor && ancestor != m_listItem.get(); ancestor = parentBox(*ancestor))
        blockOffset += ancestor->logicalTop();

    m_lineLogicalOffsetForListItem = m_listItem->logicalLeftOffsetForLine(blockOffset);
    m_lineOffsetForListItem = writingMode().isLogicalLeftInlineStart() ? m_lineLogicalOffsetForListItem : m_listItem->logicalRightOffsetForLine(blockOffset);

    if (CheckedPtr container = contentContainer()) {
        updateInlineMarginsAndContent();
        layoutContentContainer(*container);
    } else if (isImage()) {
        updateInlineMarginsAndContent();
        RefPtr image = m_image;
        setBorderBoxWidth(image->imageSize(this, style().usedZoom()).width());
        setBorderBoxHeight(image->imageSize(this, style().usedZoom()).height());
        m_layoutBounds = { borderBoxHeight(), 0 };
    } else {
        setLogicalWidth(minContentLogicalWidthContribution());
        setLogicalHeight(style().metricsOfPrimaryFont().intHeight());
        m_layoutBounds = layoutBoundForTextContent(textWithSuffix());
    }

    setMarginStart(0);
    setMarginEnd(0);

    if (auto fixedStartMargin = style().marginStart().tryFixed())
        setMarginStart(LayoutUnit(fixedStartMargin->resolveZoom(style().usedZoomForLength())));
    if (auto fixedEndMargin = style().marginEnd().tryFixed())
        setMarginEnd(LayoutUnit(fixedEndMargin->resolveZoom(style().usedZoomForLength())));

    clearNeedsLayout();
}

void RenderListMarker::layoutContentContainer(RenderBlockFlow& container)
{
    // The marker participates in its list item's line as a single atomic inline. Lay its
    // generated-content subtree out at its max-content (shrink-to-fit) width, like an
    // inline-block, then adopt the resulting size and baseline.
    auto contentLogicalWidth = container.maxContentLogicalWidthContribution();
    container.setOverridingBorderBoxLogicalWidth(contentLogicalWidth);
    container.setNeedsLayout(MarkingBehavior::MarkOnlyThis);
    container.layoutIfNeeded();
    container.clearOverridingBorderBoxLogicalWidth();

    setLogicalWidth(container.logicalWidth());
    setLogicalHeight(container.logicalHeight());

    // The inline formatting context aligns the marker box on the marker's primary-font baseline
    // (like a text marker; see InlineLineBoxBuilder), then we paint the content box at the marker
    // box origin. Offset the content box along the block axis so its own first-line baseline lands
    // on that font baseline — otherwise the content's line-box half-leading shifts it off the list
    // item's baseline. Logical setters keep this correct in vertical writing modes.
    auto markerAscent = LayoutUnit { style().metricsOfPrimaryFont().ascent() };
    auto contentBaseline = container.firstLineBaseline().value_or(container.logicalHeight());
    container.setLogicalTop(markerAscent - contentBaseline);

    m_layoutBounds = { contentBaseline, container.logicalHeight() - contentBaseline };

    // The content box can extend outside the marker's border box (its baseline offset may be
    // negative, or the content taller than the marker font), so record its overflow. The marker
    // overrides layout() and otherwise reports no overflow, which would clip/mis-repaint the content.
    clearOverflow();
    addLayoutOverflow({ container.location(), container.borderBoxSize() });
    auto contentVisualOverflow = container.visualOverflowRect();
    contentVisualOverflow.moveBy(container.location());
    addVisualOverflow(contentVisualOverflow);
}

void RenderListMarker::imageChanged(WrappedImagePtr o, const IntRect* rect)
{
    if (parent()) {
        RefPtr image = m_image;
        if (image && o == image->data()) {
            if (borderBoxWidth() != image->imageSize(this, style().usedZoom()).width() || borderBoxHeight() != image->imageSize(this, style().usedZoom()).height() || image->errorOccurred())
                setNeedsLayoutAndInvalidateContentLogicalWidths();
            else
                repaint();
        }
    }
    RenderBox::imageChanged(o, rect);
}

void RenderListMarker::updateInlineMarginsAndContent()
{
    // FIXME: It's messy to use the preferredLogicalWidths dirty bit for this optimization, also unclear if this is premature optimization.
    if (hasInvalidContentLogicalWidths())
        updateContent();
    updateInlineMargins();
}

void RenderListMarker::updateContent()
{
    if (hasContent()) {
        // css-lists-3 §3.3: `content` (not normal) supersedes list-style-image/type. The generated
        // content lives in the anonymous inline-block contentContainer(); the marker has no text/image.
        m_textContent = { };
        return;
    }

    if (isImage()) {
        // FIXME: This is a somewhat arbitrary width.
        LayoutUnit bulletWidth = style().metricsOfPrimaryFont().intAscent() / 2_lu;
        LayoutSize defaultBulletSize(bulletWidth, bulletWidth);
        LayoutSize imageSize = calculateImageIntrinsicDimensions(m_image.get(), defaultBulletSize, ScaleByUsedZoom::No);
        protect(m_image)->setContainerContextForRenderer(*this, imageSize, style().usedZoom());
        m_textContent = {
            .textWithSuffix = emptyString(),
            .textWithoutSuffixLength = 0,
            .textDirection = TextDirection::LTR,
        };
        return;
    }

    auto contentTextDirection = [&](auto content) {
        if (!content.length())
            return TextDirection::LTR;
        // FIXME: Depending on the string value, we may need the real bidi algorithm. (rdar://106139180)
        // Also we may need to start checking for the entire content for directionality (and whether we need to check for additional
        // directionality characters like U_RIGHT_TO_LEFT_EMBEDDING).
        auto bidiCategory = u_charDirection(content[0]);
        if (bidiCategory != U_RIGHT_TO_LEFT && bidiCategory != U_RIGHT_TO_LEFT_ARABIC)
            return TextDirection::LTR;
        return TextDirection::RTL;
    };

    WTF::switchOn(style().listStyleType(),
        [&](const CSS::Keyword::None&) {
            m_textContent = {
                .textWithSuffix = " "_s,
                .textWithoutSuffixLength = 0,
                .textDirection = TextDirection::LTR,
            };
        },
        [&](const Style::String& identifier) {
            m_textContent = {
                .textWithSuffix = identifier.value,
                .textWithoutSuffixLength = identifier.value.length(),
                .textDirection = contentTextDirection(StringView { identifier.value }),
            };
        },
        [&](const Style::CounterStyle&) {
            auto counter = counterStyle();
            ASSERT(counter);

            auto text = makeString(counter->prefix().text, counter->text(m_listItem->value(), writingMode()));
            m_textContent = {
                .textWithSuffix = makeString(text, counter->suffix().text),
                .textWithoutSuffixLength = text.length(),
                .textDirection = contentTextDirection(text),
            };
        }
    );
}

void RenderListMarker::computeIntrinsicLogicalWidthContributions()
{
    ASSERT(hasInvalidContentLogicalWidths());
    updateContent();

    if (CheckedPtr container = contentContainer()) {
        // The marker is non-wrapping, so its min- and max-content widths are both the
        // content's max-content width.
        auto logicalWidth = container->maxContentLogicalWidthContribution();
        m_minContentLogicalWidthContribution = logicalWidth;
        m_maxContentLogicalWidthContribution = logicalWidth;
        clearContentLogicalWidthsInvalidation();
        updateInlineMargins();
        return;
    }

    if (isImage()) {
        LayoutSize imageSize = LayoutSize(protect(m_image)->imageSize(this, style().usedZoom()));
        m_maxContentLogicalWidthContribution = writingMode().isHorizontal() ? imageSize.width() : imageSize.height();
        m_minContentLogicalWidthContribution = m_maxContentLogicalWidthContribution;
        clearContentLogicalWidthsInvalidation();
        updateInlineMargins();
        return;
    }

    std::optional<FontCascade> systemUIFontCascade;
    // Use system-ui font for disclosure triangles
    if (isDisclosureMarker())
        systemUIFontCascade = disclosureMarkerFontCascade(style(), protect(document()));

    auto& font = systemUIFontCascade ? *systemUIFontCascade : style().fontCascade();

    LayoutUnit logicalWidth;
    if (widthUsesMetricsOfPrimaryFont())
        logicalWidth = (font.metricsOfPrimaryFont().intAscent() * 2 / 3 + 1) / 2 + 2;
    else if (!m_textContent.isEmpty())
        logicalWidth = font.width(textRunForContent(m_textContent, style()));

    m_minContentLogicalWidthContribution = logicalWidth;
    m_maxContentLogicalWidthContribution = logicalWidth;

    clearContentLogicalWidthsInvalidation();

    updateInlineMargins();
}

void RenderListMarker::updateInlineMargins()
{
    constexpr int markerPadding = 7;
    const FontMetrics& fontMetrics = style().metricsOfPrimaryFont();

    auto marginsForInsideMarker = [&]() -> std::pair<LayoutUnit, LayoutUnit> {
        if (isImage())
            return { 0, markerPadding };

        if (widthUsesMetricsOfPrimaryFont())
            return { -1, fontMetrics.intAscent() - minContentLogicalWidthContribution() + 1 };

        return { };
    };

    auto marginsForOutsideMarker = [&]() -> std::pair<LayoutUnit, LayoutUnit> {
        if (isImage())
            return { -minContentLogicalWidthContribution() - markerPadding, markerPadding };

        int offset = fontMetrics.intAscent() * 2 / 3;
        if (widthUsesMetricsOfPrimaryFont())
            return { -offset - markerPadding - 1, offset + markerPadding + 1 - minContentLogicalWidthContribution() };

        if (m_textContent.isEmpty() && !contentContainer())
            return { };

        return { -minContentLogicalWidthContribution(), 0 };
    };

    auto [marginStart, marginEnd] = isInside() ? marginsForInsideMarker() : marginsForOutsideMarker();
    auto zoom = style().usedZoomForLength().value;

    mutableStyle().setMarginStart(Style::MarginEdge::Fixed { marginStart / zoom });
    mutableStyle().setMarginEnd(Style::MarginEdge::Fixed { marginEnd / zoom });
}

bool RenderListMarker::isInside() const
{
    return style().listStylePosition() == ListStylePosition::Inside;
}

bool RenderListMarker::isDisclosureMarker() const
{
    auto counter = counterStyle();
    if (!counter)
        return false;
    auto system = counter->system();
    return system == CSSCounterStyleDescriptors::System::DisclosureClosed
        || system == CSSCounterStyleDescriptors::System::DisclosureOpen;
}

const RenderListItem* RenderListMarker::listItem() const
{
    return m_listItem.get();
}

Node* RenderListMarker::nodeForHitTest() const
{
    return m_listItem ? m_listItem->element() : nullptr;
}

FloatRect RenderListMarker::relativeMarkerRect()
{
    if (isImage())
        return { 0.f, 0.f, protect(m_image)->imageSize(this, style().usedZoom()).width(), protect(m_image)->imageSize(this, style().usedZoom()).height() };

    FloatRect relativeRect;
    if (widthUsesMetricsOfPrimaryFont()) {
        auto& fontMetrics = style().metricsOfPrimaryFont();
        auto ascent = fontMetrics.ascent();
        auto bulletWidth = (ascent * 2 / 3 + 1) / 2;
        relativeRect = { 1, 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth };
    } else {
        if (m_textContent.isEmpty())
            return { };

        // Use system-ui font for disclosure triangles
        if (isDisclosureMarker()) {
            auto systemUIFontCascade = disclosureMarkerFontCascade(style(), protect(document()));
            auto& fontMetrics = style().metricsOfPrimaryFont();
            auto& systemUIFontMetrics = systemUIFontCascade.metricsOfPrimaryFont();
            auto width = systemUIFontCascade.width(textRunForContent(m_textContent, style()));
            auto height = systemUIFontMetrics.height();
            // Center vertically within the original font metrics
            auto yOffset = (fontMetrics.height() - height) / 2.0f;
            relativeRect = { 0.f, yOffset, width, height };
        } else {
            auto& font = style().fontCascade();
            relativeRect = { 0.f, 0.f, font.width(textRunForContent(m_textContent, style())), font.metricsOfPrimaryFont().height() };
        }
    }

    if (!writingMode().isHorizontal()) {
        relativeRect = relativeRect.transposedRect();
        relativeRect.setX(borderBoxWidth() - relativeRect.x() - relativeRect.width());
    }

    return relativeRect;
}

LayoutRect RenderListMarker::selectionRectForRepaint(const RenderLayerModelObject*, bool)
{
    ASSERT(!needsLayout());
    return { };
}

RefPtr<CSSRegisteredCounterStyle> RenderListMarker::counterStyle() const
{
    auto counterStyle = style().listStyleType().tryCounterStyle();
    if (!counterStyle)
        return nullptr;
    return document().counterStyleRegistry().resolvedCounterStyle(*counterStyle);
}

bool RenderListMarker::widthUsesMetricsOfPrimaryFont() const
{
    // `content` supersedes list-style-type, so a content marker never draws a bullet glyph.
    if (hasContent())
        return false;
    auto& listType = style().listStyleType();
    return listType.isCircle() || listType.isDisc() || listType.isSquare();
}

std::pair<float, float> RenderListMarker::layoutBoundForTextContent(String text) const
{
    // FIXME: This should be part of InlineBoxBuilder (webkit.org/b/294342)
    // This is essentially what we do in LineBoxBuilder::enclosingAscentDescentWithFallbackFonts.
    auto ascentAndDescent = [&] (auto& fontMetrics) {
        auto ascent = fontMetrics.ascent();
        auto descent = fontMetrics.descent();
        auto halfLeading = (fontMetrics.lineSpacing() - (ascent + descent)) / 2.f;
        return std::pair<float, float> { ascent + halfLeading, descent + halfLeading };
    };
    auto& style = this->style();
    auto& metricsOfPrimaryFont = style.metricsOfPrimaryFont();
    auto primaryFontHeight = metricsOfPrimaryFont.height();

    if (style.lineHeight().isNormal()) {
        auto maxAscentAndDescent = ascentAndDescent(metricsOfPrimaryFont);

        for (Ref fallbackFont : Layout::TextUtil::fallbackFontsForText(text, style, Layout::TextUtil::IncludeHyphen::No)) {
            auto& fontMetrics = fallbackFont->fontMetrics();
            if (primaryFontHeight >= fontMetrics.height()) {
                // FIXME: Figure out why certain symbols (e.g. disclosure-open) would initiate fallback fonts with just slightly different (subpixel) metrics.
                // This is mainly about preserving legacy behavior.
                continue;
            }
            auto ascentDescent = ascentAndDescent(fontMetrics);
            maxAscentAndDescent.first = std::max(maxAscentAndDescent.first, ascentDescent.first);
            maxAscentAndDescent.second = std::max(maxAscentAndDescent.second, ascentDescent.second);
        }
        return { maxAscentAndDescent.first, maxAscentAndDescent.second };
    }

    auto primaryFontAscentAndDescent = ascentAndDescent(metricsOfPrimaryFont);
    auto halfLeading = (style.computedLineHeight() - (primaryFontAscentAndDescent.first + primaryFontAscentAndDescent.second)) / 2.f;
    return { primaryFontAscentAndDescent.first + halfLeading, primaryFontAscentAndDescent.second + halfLeading };
}

} // namespace WebCore
