Title: [289769] trunk/Source/WebCore
Revision
289769
Author
pan...@apple.com
Date
2022-02-14 14:59:12 -0800 (Mon, 14 Feb 2022)

Log Message

Web Inspector: Element tooltips in overlays should use same encodable/decodable Label type as grid overlays
https://bugs.webkit.org/show_bug.cgi?id=235422

Reviewed by Devin Rousso.

The tooltip for elements previously used its own slightly different labels from those used for grid overlays,
which are implemented in such a way to support being sent to the UI process for iOS overlay support. This patch
adds support for the setting different colors for different runs of text along with multi-line labels to allow
the same label to be used for both Grid overlays as well as element tooltips.

The existing `WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label` was moved to
`WebCore::InspectorOverlayLabel` as it is no longer exclusively used for grids.

With that support, we can now use InspectorOverlayLabel for element tooltips without needing duplicated layout
and drawing code. Additionally, the font used in the tooltip is now consistent with grid labels, where as
previously we used different font families for the grid and element labels.

* Headers.cmake:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:

* inspector/InspectorOverlay.cpp:
(WebCore::InspectorOverlay::drawElementTitle):
(WebCore::InspectorOverlay::drawGridOverlay):
(WebCore::InspectorOverlay::buildGridOverlay):
(WebCore::fontForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `systemFont`.
(WebCore::backgroundPathForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `backgroundPath`.
(WebCore::expectedSizeForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `expectedSize`.
(WebCore::drawLayoutLabel): Moved to InspectorOverlayLabel.cpp as `draw`.
(WebCore::buildLabel): Deleted.

* inspector/InspectorOverlay.h:
(WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label::encode const): Deleted.
(WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label::decode): Deleted.

* inspector/InspectorOverlayLabel.cpp: Added.
(WebCore::InspectorOverlayLabel::InspectorOverlayLabel):
(WebCore::systemFont):
(WebCore::backgroundPath):
(WebCore::InspectorOverlayLabel::draw):
- Updated logic to handle multiple strings, including strings containing newlines. Strings are now each
converted to a TextRun, or multiple text runs for multi-line text, each of which is measured to determine the
overall height and width of the label, as well as to later in the drawing code give us the information necessary
to actually draw these strings in the proper locations.
- Use the width of the longest line for drawing the background of the label.
- Iterate through the computed TextRuns and draw the,, moving to the next line for each index that we had
previously computed to be the start of a new line.
(WebCore::InspectorOverlayLabel::expectedSize):
- Similar to `InspectorOverlayLabel::draw` we need to take in to account multi-line strings, but do not need to
keep the computed TextRuns or their widths, only the width of the longest line and the total number of lines, to
compute the expected size of the contents in a label.

* inspector/InspectorOverlayLabel.h: Added.
(WebCore::InspectorOverlayLabel::Arrow::Arrow):
(WebCore::InspectorOverlayLabel::encode const):
(WebCore::InspectorOverlayLabel::decode):
(WebCore::InspectorOverlayLabel::Arrow::encode const):
(WebCore::InspectorOverlayLabel::Arrow::decode):
(WebCore::InspectorOverlayLabel::Content::encode const):
(WebCore::InspectorOverlayLabel::Content::decode):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (289768 => 289769)


--- trunk/Source/WebCore/ChangeLog	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/ChangeLog	2022-02-14 22:59:12 UTC (rev 289769)
@@ -1,3 +1,66 @@
+2022-02-14  Patrick Angle  <pan...@apple.com>
+
+        Web Inspector: Element tooltips in overlays should use same encodable/decodable Label type as grid overlays
+        https://bugs.webkit.org/show_bug.cgi?id=235422
+
+        Reviewed by Devin Rousso.
+
+        The tooltip for elements previously used its own slightly different labels from those used for grid overlays,
+        which are implemented in such a way to support being sent to the UI process for iOS overlay support. This patch
+        adds support for the setting different colors for different runs of text along with multi-line labels to allow
+        the same label to be used for both Grid overlays as well as element tooltips.
+
+        The existing `WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label` was moved to
+        `WebCore::InspectorOverlayLabel` as it is no longer exclusively used for grids.
+
+        With that support, we can now use InspectorOverlayLabel for element tooltips without needing duplicated layout
+        and drawing code. Additionally, the font used in the tooltip is now consistent with grid labels, where as
+        previously we used different font families for the grid and element labels.
+
+        * Headers.cmake:
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+
+        * inspector/InspectorOverlay.cpp:
+        (WebCore::InspectorOverlay::drawElementTitle):
+        (WebCore::InspectorOverlay::drawGridOverlay):
+        (WebCore::InspectorOverlay::buildGridOverlay):
+        (WebCore::fontForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `systemFont`.
+        (WebCore::backgroundPathForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `backgroundPath`.
+        (WebCore::expectedSizeForLayoutLabel): Moved to InspectorOverlayLabel.cpp as `expectedSize`.
+        (WebCore::drawLayoutLabel): Moved to InspectorOverlayLabel.cpp as `draw`.
+        (WebCore::buildLabel): Deleted.
+
+        * inspector/InspectorOverlay.h:
+        (WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label::encode const): Deleted.
+        (WebCore::InspectorOverlay::Highlight::GridHighlightOverlay::Label::decode): Deleted.
+
+        * inspector/InspectorOverlayLabel.cpp: Added.
+        (WebCore::InspectorOverlayLabel::InspectorOverlayLabel):
+        (WebCore::systemFont):
+        (WebCore::backgroundPath):
+        (WebCore::InspectorOverlayLabel::draw):
+        - Updated logic to handle multiple strings, including strings containing newlines. Strings are now each
+        converted to a TextRun, or multiple text runs for multi-line text, each of which is measured to determine the
+        overall height and width of the label, as well as to later in the drawing code give us the information necessary
+        to actually draw these strings in the proper locations.
+        - Use the width of the longest line for drawing the background of the label.
+        - Iterate through the computed TextRuns and draw the,, moving to the next line for each index that we had
+        previously computed to be the start of a new line.
+        (WebCore::InspectorOverlayLabel::expectedSize):
+        - Similar to `InspectorOverlayLabel::draw` we need to take in to account multi-line strings, but do not need to
+        keep the computed TextRuns or their widths, only the width of the longest line and the total number of lines, to
+        compute the expected size of the contents in a label.
+
+        * inspector/InspectorOverlayLabel.h: Added.
+        (WebCore::InspectorOverlayLabel::Arrow::Arrow):
+        (WebCore::InspectorOverlayLabel::encode const):
+        (WebCore::InspectorOverlayLabel::decode):
+        (WebCore::InspectorOverlayLabel::Arrow::encode const):
+        (WebCore::InspectorOverlayLabel::Arrow::decode):
+        (WebCore::InspectorOverlayLabel::Content::encode const):
+        (WebCore::InspectorOverlayLabel::Content::decode):
+
 2022-02-14  Eric Carlson  <eric.carl...@apple.com>
 
         [macOS] Check feature flag before using screen/window picker

Modified: trunk/Source/WebCore/Headers.cmake (289768 => 289769)


--- trunk/Source/WebCore/Headers.cmake	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/Headers.cmake	2022-02-14 22:59:12 UTC (rev 289769)
@@ -953,6 +953,7 @@
     inspector/InspectorInstrumentationPublic.h
     inspector/InspectorInstrumentationWebKit.h
     inspector/InspectorOverlay.h
+    inspector/InspectorOverlayLabel.h
     inspector/InspectorWebAgentBase.h
     inspector/PageDebugger.h
 

Modified: trunk/Source/WebCore/Sources.txt (289768 => 289769)


--- trunk/Source/WebCore/Sources.txt	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/Sources.txt	2022-02-14 22:59:12 UTC (rev 289769)
@@ -1488,6 +1488,7 @@
 inspector/InspectorInstrumentationWebKit.cpp
 inspector/InspectorNodeFinder.cpp
 inspector/InspectorOverlay.cpp
+inspector/InspectorOverlayLabel.cpp
 inspector/InspectorShaderProgram.cpp
 inspector/InspectorStyleSheet.cpp
 inspector/InstrumentingAgents.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (289768 => 289769)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2022-02-14 22:59:12 UTC (rev 289769)
@@ -921,6 +921,7 @@
 		2ECF7ADD10162B3800427DE7 /* JSErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2ECF7ADB10162B3800427DE7 /* JSErrorEvent.h */; };
 		2ECF7AE210162B5800427DE7 /* ErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 2ECF7ADF10162B5800427DE7 /* ErrorEvent.h */; };
 		2ED609BD1145B07100C8684E /* DOMFormData.h in Headers */ = {isa = PBXBuildFile; fileRef = 2ED609BB1145B07100C8684E /* DOMFormData.h */; };
+		2EDDAF4427920EB4005D18E4 /* InspectorOverlayLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EDDAF4327920EB4005D18E4 /* InspectorOverlayLabel.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		2EDEF1F4121B0EFC00726DB2 /* BlobData.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EDEF1EE121B0EFC00726DB2 /* BlobData.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		2EDEF1F5121B0EFC00726DB2 /* BlobRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EDEF1EF121B0EFC00726DB2 /* BlobRegistry.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		2EDEF1F7121B0EFC00726DB2 /* BlobRegistryImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 2EDEF1F1121B0EFC00726DB2 /* BlobRegistryImpl.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -8234,6 +8235,8 @@
 		2ECF7AE010162B5800427DE7 /* ErrorEvent.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ErrorEvent.idl; sourceTree = "<group>"; };
 		2ED609BA1145B07100C8684E /* DOMFormData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMFormData.cpp; sourceTree = "<group>"; };
 		2ED609BB1145B07100C8684E /* DOMFormData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMFormData.h; sourceTree = "<group>"; };
+		2EDDAF4327920EB4005D18E4 /* InspectorOverlayLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InspectorOverlayLabel.h; sourceTree = "<group>"; };
+		2EDDAF4527920EC3005D18E4 /* InspectorOverlayLabel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorOverlayLabel.cpp; sourceTree = "<group>"; };
 		2EDEF1ED121B0EFC00726DB2 /* BlobData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BlobData.cpp; sourceTree = "<group>"; };
 		2EDEF1EE121B0EFC00726DB2 /* BlobData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobData.h; sourceTree = "<group>"; };
 		2EDEF1EF121B0EFC00726DB2 /* BlobRegistry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobRegistry.h; sourceTree = "<group>"; };
@@ -20402,6 +20405,8 @@
 				504AACCC1834455900E3D9BC /* InspectorNodeFinder.h */,
 				7C522D4915B477E8009B7C95 /* InspectorOverlay.cpp */,
 				7C522D4A15B478B2009B7C95 /* InspectorOverlay.h */,
+				2EDDAF4527920EC3005D18E4 /* InspectorOverlayLabel.cpp */,
+				2EDDAF4327920EB4005D18E4 /* InspectorOverlayLabel.h */,
 				6A7279891F16C29B003F39B8 /* InspectorShaderProgram.cpp */,
 				6A7279881F16C29B003F39B8 /* InspectorShaderProgram.h */,
 				82AB176F125C826700C5069D /* InspectorStyleSheet.cpp */,
@@ -35105,6 +35110,7 @@
 				A5B81CB11FAA44620037D1E6 /* InspectorNetworkAgent.h in Headers */,
 				504AACCE1834455900E3D9BC /* InspectorNodeFinder.h in Headers */,
 				0F03C0751884805500A5F8CA /* InspectorOverlay.h in Headers */,
+				2EDDAF4427920EB4005D18E4 /* InspectorOverlayLabel.h in Headers */,
 				A5B81CB21FAA44620037D1E6 /* InspectorPageAgent.h in Headers */,
 				6A72798B1F16C29C003F39B8 /* InspectorShaderProgram.h in Headers */,
 				82AB1774125C826700C5069D /* InspectorStyleSheet.h in Headers */,

Modified: trunk/Source/WebCore/inspector/InspectorOverlay.cpp (289768 => 289769)


--- trunk/Source/WebCore/inspector/InspectorOverlay.cpp	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/inspector/InspectorOverlay.cpp	2022-02-14 22:59:12 UTC (rev 289769)
@@ -79,10 +79,6 @@
 
 using namespace Inspector;
 
-static constexpr float elementDataSpacing = 2;
-static constexpr float elementDataArrowSize = 7;
-static constexpr float elementDataBorderSize = 1;
-
 static constexpr float rulerSize = 15;
 static constexpr float rulerLabelSize = 13;
 static constexpr float rulerStepIncrement = 50;
@@ -90,9 +86,6 @@
 static constexpr float rulerSubStepIncrement = 5;
 static constexpr float rulerSubStepLength = 5;
 
-static constexpr float layoutLabelPadding = 4;
-static constexpr float layoutLabelArrowSize = 6;
-
 static constexpr UChar bullet = 0x2022;
 static constexpr UChar ellipsis = 0x2026;
 static constexpr UChar multiplicationSign = 0x00D7;
@@ -1089,141 +1082,85 @@
             elementRole = axObject->computedRoleString();
     }
 
-    FontCascadeDescription fontDescription;
-    fontDescription.setFamilies({ "Menlo", m_page.settings().fixedFontFamily() });
-    fontDescription.setComputedSize(11);
+    constexpr auto elementTitleTagColor = SRGBA<uint8_t> { 136, 18, 128 }; // Keep this in sync with XMLViewer.css (.tag)
+    constexpr auto elementTitleAttributeValueColor = SRGBA<uint8_t> { 26, 26, 166 }; // Keep this in sync with XMLViewer.css (.attribute-value)
+    constexpr auto elementTitleAttributeNameColor = SRGBA<uint8_t> { 153, 69, 0 }; // Keep this in sync with XMLViewer.css (.attribute-name)
+    constexpr auto elementTitleRoleColor = SRGBA<uint8_t> { 170, 13, 145 };
 
-    FontCascade font(WTFMove(fontDescription), 0, 0);
-    font.update(nullptr);
+    Vector<InspectorOverlayLabel::Content> labelContents = {
+        { elementTagName, elementTitleTagColor },
+        { elementIDValue, elementTitleAttributeValueColor },
+        { elementClassValue, elementTitleAttributeNameColor },
+        { elementPseudoType, elementTitleTagColor },
+        { makeString(emSpace, elementWidth), Color::black },
+        { makeString("px "_s, multiplicationSign, " "_s), Color::darkGray },
+        { elementHeight, Color::black },
+        { "px"_s, Color::darkGray },
+    };
 
-    int fontHeight = font.metricsOfPrimaryFont().height();
-
-    float elementDataWidth;
-    float elementDataHeight = fontHeight;
-    bool hasSecondLine = !elementRole.isEmpty();
-
-    {
-        auto firstLine = makeString(elementTagName, elementIDValue, elementClassValue, elementPseudoType, ' ', elementWidth, "px", ' ', multiplicationSign, ' ', elementHeight, "px");
-        auto secondLine = makeString("Role ", elementRole);
-
-        float firstLineWidth = font.width(TextRun(firstLine));
-        float secondLineWidth = font.width(TextRun(secondLine));
-
-        elementDataWidth = std::fmax(firstLineWidth, secondLineWidth);
-        if (hasSecondLine)
-            elementDataHeight += fontHeight;
+    if (!elementRole.isEmpty()) {
+        labelContents.append({ "\nRole "_s, elementTitleRoleColor });
+        labelContents.append({ elementRole, Color::black });
     }
 
     FrameView* pageView = m_page.mainFrame().view();
 
     FloatSize viewportSize = pageView->sizeForVisibleContent();
-    viewportSize.expand(-elementDataSpacing, -elementDataSpacing);
-
     FloatSize contentInset(0, pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset));
-    contentInset.expand(elementDataSpacing, elementDataSpacing);
     if (m_showRulers || m_showRulersDuringElementSelection)
         contentInset.expand(rulerSize, rulerSize);
 
+    auto expectedLabelSize = InspectorOverlayLabel::expectedSize(labelContents, InspectorOverlayLabel::Arrow::Direction::Up);
+    auto boundsCenterX = bounds.center().x();
+
+    float labelX;
+    InspectorOverlayLabel::Arrow::Alignment arrowAlignment;
+    if (boundsCenterX + (expectedLabelSize.width() / 2) < viewportSize.width()
+        && boundsCenterX - (expectedLabelSize.width() / 2) > contentInset.width()) {
+        labelX = bounds.x() + (bounds.width() / 2);
+        arrowAlignment = InspectorOverlayLabel::Arrow::Alignment::Middle;
+    } else if (bounds.x() < contentInset.width()) {
+        labelX = fmax(contentInset.width(), boundsCenterX);
+        arrowAlignment = InspectorOverlayLabel::Arrow::Alignment::Leading;
+    } else if (bounds.maxX() > viewportSize.width()) {
+        labelX = fmin(viewportSize.width(), boundsCenterX);
+        arrowAlignment = InspectorOverlayLabel::Arrow::Alignment::Trailing;
+    } else {
+        labelX = boundsCenterX;
+        arrowAlignment = boundsCenterX < (viewportSize.width() / 2) ? InspectorOverlayLabel::Arrow::Alignment::Leading : InspectorOverlayLabel::Arrow::Alignment::Trailing;
+    }
+
     float anchorTop = bounds.y();
     float anchorBottom = bounds.maxY();
 
-    bool renderArrowUp = false;
-    bool renderArrowDown = false;
-
-    float boxWidth = elementDataWidth + (elementDataSpacing * 2);
-    float boxHeight = elementDataArrowSize + elementDataHeight + (elementDataSpacing * 2);
-
-    float boxX = bounds.x();
-    if (boxX < contentInset.width())
-        boxX = contentInset.width();
-    else if (boxX > viewportSize.width() - boxWidth)
-        boxX = viewportSize.width() - boxWidth;
-    else
-        boxX += elementDataSpacing;
-
-    float boxY;
+    float labelY;
+    InspectorOverlayLabel::Arrow::Direction arrowDirection;
     if (anchorTop > viewportSize.height()) {
-        boxY = viewportSize.height() - boxHeight;
-        renderArrowDown = true;
+        labelY = viewportSize.height();
+        arrowDirection = InspectorOverlayLabel::Arrow::Direction::Down;
     } else if (anchorBottom < contentInset.height()) {
-        boxY = contentInset.height() + elementDataArrowSize;
-        renderArrowUp = true;
-    } else if (anchorTop - boxHeight - elementDataSpacing > contentInset.height()) {
-        boxY = anchorTop - boxHeight - elementDataSpacing;
-        renderArrowDown = true;
-    } else if (anchorBottom + boxHeight + elementDataSpacing < viewportSize.height()) {
-        boxY = anchorBottom + elementDataArrowSize + elementDataSpacing;
-        renderArrowUp = true;
+        labelY = contentInset.height();
+        arrowDirection = InspectorOverlayLabel::Arrow::Direction::Up;
+    } else if (anchorTop - expectedLabelSize.height() > contentInset.height()) {
+        labelY = anchorTop;
+        arrowDirection = InspectorOverlayLabel::Arrow::Direction::Down;
+    } else if (anchorBottom + expectedLabelSize.height() < viewportSize.height()) {
+        labelY = anchorBottom;
+        arrowDirection = InspectorOverlayLabel::Arrow::Direction::Up;
     } else {
-        boxY = contentInset.height();
-        renderArrowDown = true;
+        labelY = contentInset.height() + expectedLabelSize.height();
+        arrowDirection = InspectorOverlayLabel::Arrow::Direction::Down;
     }
 
-    Path path;
-    path.moveTo({ boxX, boxY });
-    if (renderArrowUp) {
-        path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY });
-        path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY - elementDataArrowSize });
-        path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY });
-    }
-    path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY });
-    path.addLineTo({ boxX + elementDataWidth + (elementDataSpacing * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
-    if (renderArrowDown) {
-        path.addLineTo({ boxX + (elementDataArrowSize * 4), boxY + elementDataHeight + (elementDataSpacing * 2) });
-        path.addLineTo({ boxX + (elementDataArrowSize * 3), boxY + elementDataHeight + (elementDataSpacing * 2) + elementDataArrowSize });
-        path.addLineTo({ boxX + (elementDataArrowSize * 2), boxY + elementDataHeight + (elementDataSpacing * 2) });
-    }
-    path.addLineTo({ boxX, boxY + elementDataHeight + (elementDataSpacing * 2) });
-    path.closeSubpath();
+    constexpr auto elementTitleBackgroundColor = SRGBA<uint8_t> { 255, 255, 194 };
+    constexpr auto elementTitleBorderColor = Color::darkGray;
 
     GraphicsContextStateSaver stateSaver(context);
-
-    context.translate(elementDataBorderSize / 2.0f, elementDataBorderSize / 2.0f);
-
-    constexpr auto elementTitleBackgroundColor = SRGBA<uint8_t> { 255, 255, 194 };
-    context.setFillColor(elementTitleBackgroundColor);
-
-    context.fillPath(path);
-
-    context.setStrokeThickness(elementDataBorderSize);
-
-    constexpr auto elementTitleBorderColor = Color::darkGray;
+    context.setStrokeThickness(1);
     context.setStrokeColor(elementTitleBorderColor);
 
-    context.strokePath(path);
-
-    FloatPoint textPosition(boxX + elementDataSpacing, boxY - (elementDataSpacing / 2.0f) + fontHeight);
-    const auto drawText = [&] (const String& text, SRGBA<uint8_t> color) {
-        if (text.isEmpty())
-            return;
-
-        context.setFillColor(color);
-        textPosition += context.drawText(font, TextRun(text), textPosition);
-    };
-
-    drawText(elementTagName, { 136, 18, 128 }); // Keep this in sync with XMLViewer.css (.tag)
-    drawText(elementIDValue, { 26, 26, 166 }); // Keep this in sync with XMLViewer.css (.attribute-value)
-    drawText(elementClassValue, { 153, 69, 0 }); // Keep this in sync with XMLViewer.css (.attribute-name)
-    drawText(elementPseudoType, { 136, 18, 128 }); // Keep this in sync with XMLViewer.css (.tag)
-    drawText(" "_s, Color::black);
-    drawText(elementWidth, Color::black);
-    drawText("px"_s, Color::darkGray);
-    drawText(" "_s, Color::darkGray);
-    drawText(makeString(multiplicationSign), Color::darkGray);
-    drawText(" "_s, Color::darkGray);
-    drawText(elementHeight, Color::black);
-    drawText("px"_s, Color::darkGray);
-
-    if (hasSecondLine) {
-        textPosition.setX(boxX + elementDataSpacing);
-        textPosition.move(0, fontHeight);
-        
-        drawText("Role"_s, { 170, 13, 145 });
-        drawText(" "_s, Color::black);
-        drawText(elementRole, Color::black);
-    }
-
-    return path;
+    InspectorOverlayLabel label = { WTFMove(labelContents), { labelX, labelY }, elementTitleBackgroundColor, { arrowDirection, arrowAlignment } };
+    return label.draw(context);
 }
 
 static void drawLayoutPattern(GraphicsContext& context, const FloatQuad& quad, int hatchSpacing, Flip flip)
@@ -1283,268 +1220,6 @@
     drawLayoutPattern(context, quad, defaultLayoutHatchSpacing, flip);
 }
 
-static FontCascade fontForLayoutLabel()
-{
-    FontCascadeDescription fontDescription;
-    fontDescription.setFamilies({ "system-ui" });
-    fontDescription.setWeight(FontSelectionValue(500));
-    fontDescription.setComputedSize(12);
-
-    FontCascade font(WTFMove(fontDescription), 0, 0);
-    font.update(nullptr);
-    return font;
-}
-
-static Path backgroundPathForLayoutLabel(float width, float height, InspectorOverlay::LabelArrowDirection arrowDirection, InspectorOverlay::LabelArrowEdgePosition arrowEdgePosition, float arrowSize)
-{
-    Path path;
-    FloatSize offsetForArrowEdgePosition;
-
-    switch (arrowDirection) {
-    case InspectorOverlay::LabelArrowDirection::Down:
-        path.moveTo({ -(width / 2), -height - arrowSize});
-        path.addLineTo({ -(width / 2), -arrowSize });
-
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            path.addLineTo({ -(width / 2), 0 });
-            path.addLineTo({ -(width / 2) + arrowSize, -arrowSize });
-            offsetForArrowEdgePosition = { (width / 2), 0 };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            path.addLineTo({ -arrowSize, -arrowSize });
-            path.addLineTo({ 0, 0 });
-            path.addLineTo({ arrowSize, -arrowSize });
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            path.addLineTo({ (width / 2) - arrowSize, -arrowSize });
-            path.addLineTo({ (width / 2), 0 });
-            offsetForArrowEdgePosition = { -(width / 2), 0 };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-
-        path.addLineTo({ (width / 2), -arrowSize });
-        path.addLineTo({ (width / 2), -height - arrowSize });
-        break;
-    case InspectorOverlay::LabelArrowDirection::Up:
-        path.moveTo({ -(width / 2), height + arrowSize });
-        path.addLineTo({ -(width / 2), arrowSize });
-
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            path.addLineTo({ -(width / 2), 0 });
-            path.addLineTo({ -(width / 2) + arrowSize, arrowSize });
-            offsetForArrowEdgePosition = { (width / 2), 0 };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            path.addLineTo({ -arrowSize, arrowSize });
-            path.addLineTo({ 0, 0 });
-            path.addLineTo({ arrowSize, arrowSize });
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            path.addLineTo({ (width / 2) - arrowSize, arrowSize });
-            path.addLineTo({ (width / 2), 0 });
-            offsetForArrowEdgePosition = { -(width / 2), 0 };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-
-        path.addLineTo({ (width / 2), arrowSize });
-        path.addLineTo({ (width / 2), height + arrowSize });
-        break;
-    case InspectorOverlay::LabelArrowDirection::Right:
-        path.moveTo({ -width - arrowSize, (height / 2) });
-        path.addLineTo({ -arrowSize, (height / 2) });
-
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            path.addLineTo({ -arrowSize, -(height / 2) + arrowSize });
-            path.addLineTo({ 0, -(height / 2) });
-            offsetForArrowEdgePosition = { 0, (height / 2) };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            path.addLineTo({ -arrowSize, arrowSize });
-            path.addLineTo({ 0, 0 });
-            path.addLineTo({ -arrowSize, -arrowSize });
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            path.addLineTo({ 0, (height / 2) });
-            path.addLineTo({ -arrowSize, (height / 2) - arrowSize });
-            offsetForArrowEdgePosition = { 0, -(height / 2) };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-
-        path.addLineTo({ -arrowSize, -(height / 2) });
-        path.addLineTo({ -width - arrowSize, -(height / 2) });
-        break;
-    case InspectorOverlay::LabelArrowDirection::Left:
-        path.moveTo({ width + arrowSize, (height / 2) });
-        path.addLineTo({ arrowSize, (height / 2) });
-
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            path.addLineTo({ arrowSize, -(height / 2) + arrowSize });
-            path.addLineTo({ 0, -(height / 2) });
-            offsetForArrowEdgePosition = { 0, (height / 2) };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            path.addLineTo({ arrowSize, arrowSize });
-            path.addLineTo({ 0, 0 });
-            path.addLineTo({ arrowSize, -arrowSize });
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            path.addLineTo({ 0, (height / 2) });
-            path.addLineTo({ arrowSize, (height / 2) - arrowSize });
-            offsetForArrowEdgePosition = { 0, -(height / 2) };
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-
-        path.addLineTo({ arrowSize, -(height / 2) });
-        path.addLineTo({ width + arrowSize, -(height / 2) });
-        break;
-    case InspectorOverlay::LabelArrowDirection::None:
-        path.moveTo({ -(width / 2), -(height / 2) });
-        path.addLineTo({ -(width / 2), height / 2 });
-        path.addLineTo({ width / 2, height / 2 });
-        path.addLineTo({ width / 2, -(height / 2) });
-        break;
-    }
-
-    path.closeSubpath();
-    path.translate(offsetForArrowEdgePosition);
-
-    return path;
-}
-
-static FloatSize expectedSizeForLayoutLabel(String label, InspectorOverlay::LabelArrowDirection direction, float maximumWidth = 0)
-{
-    auto font = fontForLayoutLabel();
-
-    float textHeight = font.metricsOfPrimaryFont().floatHeight();
-    float textWidth = font.width(TextRun(label));
-    if (maximumWidth && textWidth + (layoutLabelPadding * 2) > maximumWidth)
-        textWidth = maximumWidth;
-
-    switch (direction) {
-    case InspectorOverlay::LabelArrowDirection::Down:
-    case InspectorOverlay::LabelArrowDirection::Up:
-        return { textWidth + (layoutLabelPadding * 2), textHeight + (layoutLabelPadding * 2) + layoutLabelArrowSize };
-    case InspectorOverlay::LabelArrowDirection::Right:
-    case InspectorOverlay::LabelArrowDirection::Left:
-        return { textWidth + (layoutLabelPadding * 2) + layoutLabelArrowSize, textHeight + (layoutLabelPadding * 2) };
-    case InspectorOverlay::LabelArrowDirection::None:
-        return { textWidth + (layoutLabelPadding * 2), textHeight + (layoutLabelPadding * 2) };
-    }
-
-    RELEASE_ASSERT_NOT_REACHED();
-}
-
-static void drawLayoutLabel(GraphicsContext& context, String label, FloatPoint point, InspectorOverlay::LabelArrowDirection arrowDirection, InspectorOverlay::LabelArrowEdgePosition arrowEdgePosition, Color backgroundColor, float maximumWidth = 0)
-{
-    ASSERT(arrowEdgePosition != InspectorOverlay::LabelArrowEdgePosition::None || arrowDirection == InspectorOverlay::LabelArrowDirection::None);
-
-    GraphicsContextStateSaver saver(context);
-    
-    context.translate(point);
-
-    auto font = fontForLayoutLabel();
-    float textHeight = font.metricsOfPrimaryFont().floatHeight();
-    float textDescent = font.metricsOfPrimaryFont().floatDescent();
-    
-    float textWidth = font.width(TextRun(label));
-    if (maximumWidth && textWidth + (layoutLabelPadding * 2) > maximumWidth) {
-        label.append("..."_s);
-        while (textWidth + (layoutLabelPadding * 2) > maximumWidth && label.length() >= 4) {
-            // Remove the fourth from last character (the character before the ellipsis) and remeasure.
-            label.remove(label.length() - 4);
-            textWidth = font.width(TextRun(label));
-        }
-    }
-
-    FloatPoint textPosition;
-    switch (arrowDirection) {
-    case InspectorOverlay::LabelArrowDirection::Down:
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            textPosition = FloatPoint(layoutLabelPadding, -textDescent - layoutLabelArrowSize - layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            textPosition = FloatPoint(-(textWidth / 2), -textDescent - layoutLabelArrowSize - layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            textPosition = FloatPoint(-(textWidth) - layoutLabelPadding, -textDescent - layoutLabelArrowSize - layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-        break;
-    case InspectorOverlay::LabelArrowDirection::Up:
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            textPosition = FloatPoint(layoutLabelPadding, textHeight - textDescent + layoutLabelArrowSize + layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            textPosition = FloatPoint(-(textWidth / 2), textHeight - textDescent + layoutLabelArrowSize + layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            textPosition = FloatPoint(-(textWidth) - layoutLabelPadding, textHeight - textDescent + layoutLabelArrowSize + layoutLabelPadding);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-        break;
-    case InspectorOverlay::LabelArrowDirection::Right:
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            textPosition = FloatPoint(-textWidth - layoutLabelArrowSize - layoutLabelPadding, layoutLabelPadding + textHeight - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            textPosition = FloatPoint(-textWidth - layoutLabelArrowSize - layoutLabelPadding, (textHeight / 2) - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            textPosition = FloatPoint(-textWidth - layoutLabelArrowSize - layoutLabelPadding, -layoutLabelPadding - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-        break;
-    case InspectorOverlay::LabelArrowDirection::Left:
-        switch (arrowEdgePosition) {
-        case InspectorOverlay::LabelArrowEdgePosition::Leading:
-            textPosition = FloatPoint(layoutLabelArrowSize + layoutLabelPadding, layoutLabelPadding + textHeight - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Middle:
-            textPosition = FloatPoint(layoutLabelArrowSize + layoutLabelPadding, (textHeight / 2) - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::Trailing:
-            textPosition = FloatPoint(layoutLabelArrowSize + layoutLabelPadding, -layoutLabelPadding - textDescent);
-            break;
-        case InspectorOverlay::LabelArrowEdgePosition::None:
-            break;
-        }
-        break;
-    case InspectorOverlay::LabelArrowDirection::None:
-        textPosition = FloatPoint(-(textWidth / 2), (textHeight / 2) - textDescent);
-        break;
-    }
-
-    Path labelPath = backgroundPathForLayoutLabel(textWidth + (layoutLabelPadding * 2), textHeight + (layoutLabelPadding * 2), arrowDirection, arrowEdgePosition, layoutLabelArrowSize);
-
-    context.setFillColor(backgroundColor);
-    context.fillPath(labelPath);
-    context.strokePath(labelPath);
-    
-    context.setFillColor(Color::black);
-    context.drawText(font, TextRun(label), textPosition);
-}
-
 void InspectorOverlay::drawGridOverlay(GraphicsContext& context, const InspectorOverlay::Highlight::GridHighlightOverlay& gridOverlay)
 {
     constexpr auto translucentLabelBackgroundColor = Color::white.colorWithAlphaByte(230);
@@ -1570,10 +1245,10 @@
     // Draw labels on top of all other lines.
     context.setStrokeThickness(1);
     for (auto area : gridOverlay.areas)
-        drawLayoutLabel(context, area.name, area.quad.center(), LabelArrowDirection::None, LabelArrowEdgePosition::None, translucentLabelBackgroundColor, area.quad.boundingBox().width());
+        InspectorOverlayLabel(area.name, area.quad.center(), translucentLabelBackgroundColor, { InspectorOverlayLabel::Arrow::Direction::None, InspectorOverlayLabel::Arrow::Alignment::None }).draw(context, area.quad.boundingBox().width());
 
     for (auto label : gridOverlay.labels)
-        drawLayoutLabel(context, label.text, label.location, label.arrowDirection, label.arrowEdgePosition, label.backgroundColor);
+        label.draw(context);
 }
 
 static Vector<String> authoredGridTrackSizes(Node* node, GridTrackSizingDirection direction, unsigned expectedTrackCount)
@@ -1674,17 +1349,6 @@
     return combinedGridLineNames;
 }
 
-static InspectorOverlay::Highlight::GridHighlightOverlay::Label buildLabel(String text, FloatPoint location, Color backgroundColor, InspectorOverlay::LabelArrowDirection arrowDirection, InspectorOverlay::LabelArrowEdgePosition arrowEdgePosition)
-{
-    InspectorOverlay::Highlight::GridHighlightOverlay::Label label;
-    label.text = text;
-    label.location = location;
-    label.backgroundColor = backgroundColor;
-    label.arrowDirection = arrowDirection;
-    label.arrowEdgePosition = arrowEdgePosition;
-    return label;
-}
-
 std::optional<InspectorOverlay::Highlight::GridHighlightOverlay> InspectorOverlay::buildGridOverlay(const InspectorOverlay::Grid& gridOverlay, bool offsetBoundsByScroll)
 {
     // If the node WeakPtr has been cleared, then the node is gone and there's nothing to draw.
@@ -1771,22 +1435,22 @@
         };
     };
 
-    auto correctedArrowDirection = [&](LabelArrowDirection direction, GridTrackSizingDirection sizingDirection) -> LabelArrowDirection {
+    auto correctedArrowDirection = [&](InspectorOverlayLabel::Arrow::Direction direction, GridTrackSizingDirection sizingDirection) -> InspectorOverlayLabel::Arrow::Direction {
         if ((sizingDirection == GridTrackSizingDirection::ForColumns && isWritingModeFlipped) || (sizingDirection == GridTrackSizingDirection::ForRows && isDirectionFlipped)) {
             switch (direction) {
-            case LabelArrowDirection::Down:
-                direction = LabelArrowDirection::Up;
+            case InspectorOverlayLabel::Arrow::Direction::Down:
+                direction = InspectorOverlayLabel::Arrow::Direction::Up;
                 break;
-            case LabelArrowDirection::Up:
-                direction = LabelArrowDirection::Down;
+            case InspectorOverlayLabel::Arrow::Direction::Up:
+                direction = InspectorOverlayLabel::Arrow::Direction::Down;
                 break;
-            case LabelArrowDirection::Left:
-                direction = LabelArrowDirection::Right;
+            case InspectorOverlayLabel::Arrow::Direction::Left:
+                direction = InspectorOverlayLabel::Arrow::Direction::Right;
                 break;
-            case LabelArrowDirection::Right:
-                direction = LabelArrowDirection::Left;
+            case InspectorOverlayLabel::Arrow::Direction::Right:
+                direction = InspectorOverlayLabel::Arrow::Direction::Left;
                 break;
-            case LabelArrowDirection::None:
+            case InspectorOverlayLabel::Arrow::Direction::None:
                 break;
             }
         }
@@ -1793,19 +1457,19 @@
 
         if (!isHorizontalWritingMode) {
             switch (direction) {
-            case LabelArrowDirection::Down:
-                direction = LabelArrowDirection::Right;
+            case InspectorOverlayLabel::Arrow::Direction::Down:
+                direction = InspectorOverlayLabel::Arrow::Direction::Right;
                 break;
-            case LabelArrowDirection::Up:
-                direction = LabelArrowDirection::Left;
+            case InspectorOverlayLabel::Arrow::Direction::Up:
+                direction = InspectorOverlayLabel::Arrow::Direction::Left;
                 break;
-            case LabelArrowDirection::Left:
-                direction = LabelArrowDirection::Up;
+            case InspectorOverlayLabel::Arrow::Direction::Left:
+                direction = InspectorOverlayLabel::Arrow::Direction::Up;
                 break;
-            case LabelArrowDirection::Right:
-                direction = LabelArrowDirection::Down;
+            case InspectorOverlayLabel::Arrow::Direction::Right:
+                direction = InspectorOverlayLabel::Arrow::Direction::Down;
                 break;
-            case LabelArrowDirection::None:
+            case InspectorOverlayLabel::Arrow::Direction::None:
                 break;
             }
         }
@@ -1813,15 +1477,15 @@
         return direction;
     };
 
-    auto correctedArrowEdgePosition = [&](LabelArrowEdgePosition edgePosition, GridTrackSizingDirection sizingDirection) -> LabelArrowEdgePosition {
+    auto correctedArrowAlignment = [&](InspectorOverlayLabel::Arrow::Alignment alignment, GridTrackSizingDirection sizingDirection) -> InspectorOverlayLabel::Arrow::Alignment {
         if ((sizingDirection == GridTrackSizingDirection::ForRows && isWritingModeFlipped) || (sizingDirection == GridTrackSizingDirection::ForColumns && isDirectionFlipped)) {
-            if (edgePosition == LabelArrowEdgePosition::Leading)
-                return LabelArrowEdgePosition::Trailing;
-            if (edgePosition == LabelArrowEdgePosition::Trailing)
-                return LabelArrowEdgePosition::Leading;
+            if (alignment == InspectorOverlayLabel::Arrow::Alignment::Leading)
+                return InspectorOverlayLabel::Arrow::Alignment::Trailing;
+            if (alignment == InspectorOverlayLabel::Arrow::Alignment::Trailing)
+                return InspectorOverlayLabel::Arrow::Alignment::Leading;
         }
 
-        return edgePosition;
+        return alignment;
     };
 
     InspectorOverlay::Highlight::GridHighlightOverlay gridHighlightOverlay;
@@ -1865,7 +1529,7 @@
             if (gridOverlay.config.showTrackSizes) {
                 auto authoredTrackSize = i < authoredTrackColumnSizes.size() ? authoredTrackColumnSizes[i] : "auto"_s;
                 FloatLine trackTopLine = { columnStartLine.start(), columnEndLine.start() };
-                gridHighlightOverlay.labels.append(buildLabel(authoredTrackSize, trackTopLine.pointAtRelativeDistance(0.5), translucentLabelBackgroundColor, correctedArrowDirection(LabelArrowDirection::Up, GridTrackSizingDirection::ForColumns), LabelArrowEdgePosition::Middle));
+                gridHighlightOverlay.labels.append({ authoredTrackSize, trackTopLine.pointAtRelativeDistance(0.5), translucentLabelBackgroundColor, { correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Up, GridTrackSizingDirection::ForColumns), InspectorOverlayLabel::Arrow::Alignment::Middle } });
             }
         } else
             previousColumnEndLine = columnStartLine;
@@ -1886,21 +1550,21 @@
 
         if (!lineLabel.isEmpty()) {
             auto text = lineLabel.toString();
-            auto arrowDirection = correctedArrowDirection(LabelArrowDirection::Down, GridTrackSizingDirection::ForColumns);
-            auto arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Middle, GridTrackSizingDirection::ForColumns);
+            auto arrowDirection = correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Down, GridTrackSizingDirection::ForColumns);
+            auto arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Middle, GridTrackSizingDirection::ForColumns);
 
             if (!i)
-                arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Leading, GridTrackSizingDirection::ForColumns);
+                arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Leading, GridTrackSizingDirection::ForColumns);
             else if (i == columnPositions.size() - 1)
-                arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Trailing, GridTrackSizingDirection::ForColumns);
+                arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Trailing, GridTrackSizingDirection::ForColumns);
 
-            auto expectedLabelSize = expectedSizeForLayoutLabel(text, arrowDirection);
+            auto expectedLabelSize = InspectorOverlayLabel::expectedSize(text, arrowDirection);
             auto gapLabelPosition = gapLabelLine.start();
 
             // The area under the window's toolbar is drawable, but not meaningfully visible, so we must account for that space.
             auto topEdgeInset = pageView->topContentInset(ScrollView::TopContentInsetType::WebCoreOrPlatformContentInset);
             if (gapLabelLine.start().y() - expectedLabelSize.height() - topEdgeInset + scrollPosition.y() - viewportBounds.y() < 0) {
-                arrowDirection = correctedArrowDirection(LabelArrowDirection::Up, GridTrackSizingDirection::ForColumns);
+                arrowDirection = correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Up, GridTrackSizingDirection::ForColumns);
 
                 // Special case for the first column to make sure the label will be out of the way of the first row's label.
                 // The label heights will be the same, as they use the same font, so moving down by this label's size will
@@ -1909,7 +1573,7 @@
                     gapLabelPosition = gapLabelLine.pointAtAbsoluteDistance(expectedLabelSize.height());
             }
 
-            gridHighlightOverlay.labels.append(buildLabel(text, gapLabelPosition, translucentLabelBackgroundColor, arrowDirection, arrowEdgePosition));
+            gridHighlightOverlay.labels.append({ text, gapLabelPosition, translucentLabelBackgroundColor, { arrowDirection, arrowAlignment } });
         }
     }
 
@@ -1949,7 +1613,7 @@
             if (gridOverlay.config.showTrackSizes) {
                 auto authoredTrackSize = i < authoredTrackRowSizes.size() ? authoredTrackRowSizes[i] : "auto"_s;
                 FloatLine trackLeftLine = { rowStartLine.start(), rowEndLine.start() };
-                gridHighlightOverlay.labels.append(buildLabel(authoredTrackSize, trackLeftLine.pointAtRelativeDistance(0.5), translucentLabelBackgroundColor, correctedArrowDirection(LabelArrowDirection::Left, GridTrackSizingDirection::ForRows), LabelArrowEdgePosition::Middle));
+                gridHighlightOverlay.labels.append({ authoredTrackSize, trackLeftLine.pointAtRelativeDistance(0.5), translucentLabelBackgroundColor, { correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Left, GridTrackSizingDirection::ForRows), InspectorOverlayLabel::Arrow::Alignment::Middle } });
             }
         } else
             previousRowEndLine = rowStartLine;
@@ -1970,19 +1634,19 @@
 
         if (!lineLabel.isEmpty()) {
             auto text = lineLabel.toString();
-            auto arrowDirection = correctedArrowDirection(LabelArrowDirection::Right, GridTrackSizingDirection::ForRows);
-            auto arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Middle, GridTrackSizingDirection::ForRows);
+            auto arrowDirection = correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Right, GridTrackSizingDirection::ForRows);
+            auto arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Middle, GridTrackSizingDirection::ForRows);
 
             if (!i)
-                arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Leading, GridTrackSizingDirection::ForRows);
+                arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Leading, GridTrackSizingDirection::ForRows);
             else if (i == rowPositions.size() - 1)
-                arrowEdgePosition = correctedArrowEdgePosition(LabelArrowEdgePosition::Trailing, GridTrackSizingDirection::ForRows);
+                arrowAlignment = correctedArrowAlignment(InspectorOverlayLabel::Arrow::Alignment::Trailing, GridTrackSizingDirection::ForRows);
 
-            auto expectedLabelSize = expectedSizeForLayoutLabel(text, arrowDirection);
+            auto expectedLabelSize = InspectorOverlayLabel::expectedSize(text, arrowDirection);
             if (gapLabelPosition.x() - expectedLabelSize.width() + scrollPosition.x() - viewportBounds.x() < 0)
-                arrowDirection = correctedArrowDirection(LabelArrowDirection::Left, GridTrackSizingDirection::ForRows);
+                arrowDirection = correctedArrowDirection(InspectorOverlayLabel::Arrow::Direction::Left, GridTrackSizingDirection::ForRows);
 
-            gridHighlightOverlay.labels.append(buildLabel(text, gapLabelPosition, translucentLabelBackgroundColor, arrowDirection, arrowEdgePosition));
+            gridHighlightOverlay.labels.append({ text, gapLabelPosition, translucentLabelBackgroundColor, { arrowDirection, arrowAlignment } });
         }
     }
 

Modified: trunk/Source/WebCore/inspector/InspectorOverlay.h (289768 => 289769)


--- trunk/Source/WebCore/inspector/InspectorOverlay.h	2022-02-14 22:56:41 UTC (rev 289768)
+++ trunk/Source/WebCore/inspector/InspectorOverlay.h	2022-02-14 22:59:12 UTC (rev 289769)
@@ -33,6 +33,7 @@
 #include "FloatLine.h"
 #include "FloatQuad.h"
 #include "FloatRect.h"
+#include "InspectorOverlayLabel.h"
 #include "Path.h"
 #include "Timer.h"
 #include <wtf/Deque.h>
@@ -66,21 +67,6 @@
     InspectorOverlay(Page&, InspectorClient*);
     ~InspectorOverlay();
 
-    enum class LabelArrowDirection {
-        None,
-        Down,
-        Up,
-        Left,
-        Right,
-    };
-
-    enum class LabelArrowEdgePosition {
-        None,
-        Leading, // Positioned at the left/top side of edge.
-        Middle, // Positioned at the center on the edge.
-        Trailing, // Positioned at the right/bottom side of the edge.
-    };
-
     struct Highlight {
         WTF_MAKE_STRUCT_FAST_ALLOCATED;
 
@@ -105,20 +91,6 @@
         struct GridHighlightOverlay {
             WTF_MAKE_STRUCT_FAST_ALLOCATED;
 
-            struct Label {
-                WTF_MAKE_STRUCT_FAST_ALLOCATED;
-                String text;
-                FloatPoint location;
-                Color backgroundColor;
-                LabelArrowDirection arrowDirection;
-                LabelArrowEdgePosition arrowEdgePosition;
-
-#if PLATFORM(IOS_FAMILY)
-                template<class Encoder> void encode(Encoder&) const;
-                template<class Decoder> static std::optional<InspectorOverlay::Highlight::GridHighlightOverlay::Label> decode(Decoder&);
-#endif
-            };
-
             struct Area {
                 WTF_MAKE_STRUCT_FAST_ALLOCATED;
                 String name;
@@ -134,7 +106,7 @@
             Vector<FloatLine> gridLines;
             Vector<FloatQuad> gaps;
             Vector<Area> areas;
-            Vector<Label> labels;
+            Vector<InspectorOverlayLabel> labels;
 
 #if PLATFORM(IOS_FAMILY)
             template<class Encoder> void encode(Encoder&) const;
@@ -361,38 +333,6 @@
     return { gridHighlightOverlay };
 }
 
-template<class Encoder> void InspectorOverlay::Highlight::GridHighlightOverlay::Label::encode(Encoder& encoder) const
-{
-    encoder << text;
-    encoder << location;
-    encoder << backgroundColor;
-    encoder << static_cast<uint32_t>(arrowDirection);
-    encoder << static_cast<uint32_t>(arrowEdgePosition);
-}
-
-template<class Decoder> std::optional<InspectorOverlay::Highlight::GridHighlightOverlay::Label> InspectorOverlay::Highlight::GridHighlightOverlay::Label::decode(Decoder& decoder)
-{
-    InspectorOverlay::Highlight::GridHighlightOverlay::Label label;
-    if (!decoder.decode(label.text))
-        return { };
-    if (!decoder.decode(label.location))
-        return { };
-    if (!decoder.decode(label.backgroundColor))
-        return { };
-
-    uint32_t arrowDirection;
-    if (!decoder.decode(arrowDirection))
-        return { };
-    label.arrowDirection = (InspectorOverlay::LabelArrowDirection)arrowDirection;
-
-    uint32_t arrowEdgePosition;
-    if (!decoder.decode(arrowEdgePosition))
-        return { };
-    label.arrowEdgePosition = (InspectorOverlay::LabelArrowEdgePosition)arrowEdgePosition;
-
-    return { label };
-}
-
 template<class Encoder> void InspectorOverlay::Highlight::GridHighlightOverlay::Area::encode(Encoder& encoder) const
 {
     encoder << name;

Added: trunk/Source/WebCore/inspector/InspectorOverlayLabel.cpp (0 => 289769)


--- trunk/Source/WebCore/inspector/InspectorOverlayLabel.cpp	                        (rev 0)
+++ trunk/Source/WebCore/inspector/InspectorOverlayLabel.cpp	2022-02-14 22:59:12 UTC (rev 289769)
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorOverlayLabel.h"
+
+#include "FloatSize.h"
+#include "FontCascade.h"
+#include "FontCascadeDescription.h"
+#include "GraphicsContext.h"
+#include "Path.h"
+
+namespace WebCore {
+
+static constexpr float labelPadding = 4;
+static constexpr float labelArrowSize = 6;
+
+InspectorOverlayLabel::InspectorOverlayLabel(Vector<Content>&& contents, FloatPoint location, Color backgroundColor, Arrow arrow)
+    : m_contents(WTFMove(contents))
+    , m_location(location)
+    , m_backgroundColor(backgroundColor)
+    , m_arrow(arrow)
+{
+}
+
+InspectorOverlayLabel::InspectorOverlayLabel(const String& text, FloatPoint location, Color backgroundColor, Arrow arrow)
+    : InspectorOverlayLabel({ { text, Color::black } }, location, backgroundColor, arrow)
+{
+}
+
+static FontCascade systemFont()
+{
+    FontCascadeDescription fontDescription;
+    fontDescription.setFamilies({ "system-ui" });
+    fontDescription.setWeight(FontSelectionValue(500));
+    fontDescription.setComputedSize(12);
+
+    FontCascade font(WTFMove(fontDescription), 0, 0);
+    font.update(nullptr);
+    return font;
+}
+
+static Path backgroundPath(float width, float height, InspectorOverlayLabel::Arrow arrow, float arrowSize)
+{
+    Path path;
+    FloatSize offsetForArrowEdgePosition;
+
+    switch (arrow.direction) {
+    case InspectorOverlayLabel::Arrow::Direction::Down:
+        path.moveTo({ -(width / 2), -height - arrowSize });
+        path.addLineTo({ -(width / 2), -arrowSize });
+
+        switch (arrow.alignment) {
+        case InspectorOverlayLabel::Arrow::Alignment::Leading:
+            path.addLineTo({ -(width / 2), 0 });
+            path.addLineTo({ -(width / 2) + arrowSize, -arrowSize });
+            offsetForArrowEdgePosition = { (width / 2), 0 };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Middle:
+            path.addLineTo({ -arrowSize, -arrowSize });
+            path.addLineTo({ 0, 0 });
+            path.addLineTo({ arrowSize, -arrowSize });
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Trailing:
+            path.addLineTo({ (width / 2) - arrowSize, -arrowSize });
+            path.addLineTo({ (width / 2), 0 });
+            offsetForArrowEdgePosition = { -(width / 2), 0 };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::None:
+            break;
+        }
+
+        path.addLineTo({ (width / 2), -arrowSize });
+        path.addLineTo({ (width / 2), -height - arrowSize });
+        break;
+    case InspectorOverlayLabel::Arrow::Direction::Up:
+        path.moveTo({ -(width / 2), height + arrowSize });
+        path.addLineTo({ -(width / 2), arrowSize });
+
+        switch (arrow.alignment) {
+        case InspectorOverlayLabel::Arrow::Alignment::Leading:
+            path.addLineTo({ -(width / 2), 0 });
+            path.addLineTo({ -(width / 2) + arrowSize, arrowSize });
+            offsetForArrowEdgePosition = { (width / 2), 0 };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Middle:
+            path.addLineTo({ -arrowSize, arrowSize });
+            path.addLineTo({ 0, 0 });
+            path.addLineTo({ arrowSize, arrowSize });
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Trailing:
+            path.addLineTo({ (width / 2) - arrowSize, arrowSize });
+            path.addLineTo({ (width / 2), 0 });
+            offsetForArrowEdgePosition = { -(width / 2), 0 };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::None:
+            break;
+        }
+
+        path.addLineTo({ (width / 2), arrowSize });
+        path.addLineTo({ (width / 2), height + arrowSize });
+        break;
+    case InspectorOverlayLabel::Arrow::Direction::Right:
+        path.moveTo({ -width - arrowSize, (height / 2) });
+        path.addLineTo({ -arrowSize, (height / 2) });
+
+        switch (arrow.alignment) {
+        case InspectorOverlayLabel::Arrow::Alignment::Leading:
+            path.addLineTo({ -arrowSize, -(height / 2) + arrowSize });
+            path.addLineTo({ 0, -(height / 2) });
+            offsetForArrowEdgePosition = { 0, (height / 2) };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Middle:
+            path.addLineTo({ -arrowSize, arrowSize });
+            path.addLineTo({ 0, 0 });
+            path.addLineTo({ -arrowSize, -arrowSize });
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Trailing:
+            path.addLineTo({ 0, (height / 2) });
+            path.addLineTo({ -arrowSize, (height / 2) - arrowSize });
+            offsetForArrowEdgePosition = { 0, -(height / 2) };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::None:
+            break;
+        }
+
+        path.addLineTo({ -arrowSize, -(height / 2) });
+        path.addLineTo({ -width - arrowSize, -(height / 2) });
+        break;
+    case InspectorOverlayLabel::Arrow::Direction::Left:
+        path.moveTo({ width + arrowSize, (height / 2) });
+        path.addLineTo({ arrowSize, (height / 2) });
+
+        switch (arrow.alignment) {
+        case InspectorOverlayLabel::Arrow::Alignment::Leading:
+            path.addLineTo({ arrowSize, -(height / 2) + arrowSize });
+            path.addLineTo({ 0, -(height / 2) });
+            offsetForArrowEdgePosition = { 0, (height / 2) };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Middle:
+            path.addLineTo({ arrowSize, arrowSize });
+            path.addLineTo({ 0, 0 });
+            path.addLineTo({ arrowSize, -arrowSize });
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::Trailing:
+            path.addLineTo({ 0, (height / 2) });
+            path.addLineTo({ arrowSize, (height / 2) - arrowSize });
+            offsetForArrowEdgePosition = { 0, -(height / 2) };
+            break;
+        case InspectorOverlayLabel::Arrow::Alignment::None:
+            break;
+        }
+
+        path.addLineTo({ arrowSize, -(height / 2) });
+        path.addLineTo({ width + arrowSize, -(height / 2) });
+        break;
+    case InspectorOverlayLabel::Arrow::Direction::None:
+        path.moveTo({ -(width / 2), -(height / 2) });
+        path.addLineTo({ -(width / 2), height / 2 });
+        path.addLineTo({ width / 2, height / 2 });
+        path.addLineTo({ width / 2, -(height / 2) });
+        break;
+    }
+
+    path.closeSubpath();
+    path.translate(offsetForArrowEdgePosition);
+
+    return path;
+}
+
+Path InspectorOverlayLabel::draw(GraphicsContext& context, float maximumLineWidth)
+{
+    constexpr UChar ellipsis = 0x2026;
+
+    auto font = systemFont();
+    float lineHeight = font.metricsOfPrimaryFont().floatHeight();
+    float lineDescent = font.metricsOfPrimaryFont().floatDescent();
+
+    Vector<TextRun> textRuns;
+    Vector<Color> textColors;
+    Vector<float> textWidths;
+    Vector<size_t> lineStartIndexes;
+    float longestLineWidth = 0;
+    int currentLine = 0;
+
+    float currentLineWidth = 0;
+    for (auto content : m_contents) {
+        auto lines = content.text.splitAllowingEmptyEntries('\n');
+        for (size_t i = 0; i < lines.size(); ++i) {
+            if (i) {
+                lineStartIndexes.append(textRuns.size());
+                currentLineWidth = 0;
+                ++currentLine;
+            }
+
+            auto text = lines[i];
+            if (text.isEmpty())
+                continue;
+
+            auto textRun = TextRun(text);
+            float textWidth = font.width(textRun);
+
+            if (maximumLineWidth && currentLineWidth + textWidth + (labelPadding * 2) > maximumLineWidth) {
+                text.append(ellipsis);
+                while (currentLineWidth + textWidth + (labelPadding * 2) > maximumLineWidth && text.length() > 1) {
+                    // Remove the second from last character (the character before the ellipsis) and remeasure.
+                    text.remove(text.length() - 2);
+                    textRun = TextRun(text);
+                    textWidth = font.width(textRun);
+                }
+            }
+
+            textRuns.append(WTFMove(textRun));
+            textColors.append(content.textColor);
+            textWidths.append(textWidth);
+
+            currentLineWidth += textWidth;
+            if (currentLineWidth > longestLineWidth)
+                longestLineWidth = currentLineWidth;
+        }
+    }
+
+    float totalTextHeight = lineHeight * (currentLine + 1);
+
+    FloatPoint textPosition;
+    switch (m_arrow.direction) {
+    case Arrow::Direction::Down:
+        switch (m_arrow.alignment) {
+        case Arrow::Alignment::Leading:
+            textPosition = FloatPoint(labelPadding, lineHeight - totalTextHeight - lineDescent - labelArrowSize - labelPadding);
+            break;
+        case Arrow::Alignment::Middle:
+            textPosition = FloatPoint(-(longestLineWidth / 2), lineHeight - totalTextHeight - lineDescent - labelArrowSize - labelPadding);
+            break;
+        case Arrow::Alignment::Trailing:
+            textPosition = FloatPoint(-longestLineWidth - labelPadding, lineHeight - totalTextHeight - lineDescent - labelArrowSize - labelPadding);
+            break;
+        case Arrow::Alignment::None:
+            break;
+        }
+        break;
+    case Arrow::Direction::Up:
+        switch (m_arrow.alignment) {
+        case Arrow::Alignment::Leading:
+            textPosition = FloatPoint(labelPadding, lineHeight - lineDescent + labelArrowSize + labelPadding);
+            break;
+        case Arrow::Alignment::Middle:
+            textPosition = FloatPoint(-(longestLineWidth / 2), lineHeight - lineDescent + labelArrowSize + labelPadding);
+            break;
+        case Arrow::Alignment::Trailing:
+            textPosition = FloatPoint(-longestLineWidth - labelPadding, lineHeight - lineDescent + labelArrowSize + labelPadding);
+            break;
+        case Arrow::Alignment::None:
+            break;
+        }
+        break;
+    case Arrow::Direction::Right:
+        switch (m_arrow.alignment) {
+        case Arrow::Alignment::Leading:
+            textPosition = FloatPoint(-longestLineWidth - labelArrowSize - labelPadding, labelPadding + lineHeight - lineDescent);
+            break;
+        case Arrow::Alignment::Middle:
+            textPosition = FloatPoint(-longestLineWidth - labelArrowSize - labelPadding, lineHeight - (totalTextHeight / 2) - lineDescent);
+            break;
+        case Arrow::Alignment::Trailing:
+            textPosition = FloatPoint(-longestLineWidth - labelArrowSize - labelPadding, lineHeight - totalTextHeight - labelPadding - lineDescent);
+            break;
+        case Arrow::Alignment::None:
+            break;
+        }
+        break;
+    case Arrow::Direction::Left:
+        switch (m_arrow.alignment) {
+        case Arrow::Alignment::Leading:
+            textPosition = FloatPoint(labelArrowSize + labelPadding, labelPadding + lineHeight - lineDescent);
+            break;
+        case Arrow::Alignment::Middle:
+            textPosition = FloatPoint(labelArrowSize + labelPadding, lineHeight - (totalTextHeight / 2) - lineDescent);
+            break;
+        case Arrow::Alignment::Trailing:
+            textPosition = FloatPoint(labelArrowSize + labelPadding, lineHeight - totalTextHeight - labelPadding - lineDescent);
+            break;
+        case Arrow::Alignment::None:
+            break;
+        }
+        break;
+    case Arrow::Direction::None:
+        textPosition = FloatPoint(-(longestLineWidth / 2), -(totalTextHeight / 2) + lineHeight - lineDescent);
+        break;
+    }
+
+    Path labelPath = backgroundPath(longestLineWidth + (labelPadding * 2), totalTextHeight + (labelPadding * 2), m_arrow, labelArrowSize);
+
+    GraphicsContextStateSaver saver(context);
+    context.translate(m_location);
+
+    context.setFillColor(m_backgroundColor);
+    context.fillPath(labelPath);
+    context.strokePath(labelPath);
+
+    int line = 0;
+    float xOffset = 0;
+    for (size_t i = 0; i < textRuns.size(); ++i) {
+        if (lineStartIndexes.contains(i)) {
+            xOffset = 0;
+            ++line;
+        }
+
+        context.setFillColor(textColors[i]);
+        context.drawText(font, textRuns[i], textPosition + FloatPoint(xOffset, line * lineHeight));
+
+        xOffset += textWidths[i];
+    }
+
+    return labelPath;
+}
+
+FloatSize InspectorOverlayLabel::expectedSize(const Vector<Content>& contents, Arrow::Direction direction)
+{
+    auto font = systemFont();
+    float lineHeight = font.metricsOfPrimaryFont().floatHeight();
+
+    float longestLineWidth = 0;
+    int currentLine = 0;
+
+    float currentLineWidth = 0;
+    for (auto content : contents) {
+        auto lines = content.text.splitAllowingEmptyEntries('\n');
+        for (size_t i = 0; i < lines.size(); ++i) {
+            if (i) {
+                currentLineWidth = 0;
+                ++currentLine;
+            }
+
+            auto text = lines[i];
+            if (text.isEmpty())
+                continue;
+
+            currentLineWidth += font.width(TextRun(text));
+            if (currentLineWidth > longestLineWidth)
+                longestLineWidth = currentLineWidth;
+        }
+    }
+
+    float totalTextHeight = lineHeight * (currentLine + 1);
+
+    switch (direction) {
+    case Arrow::Direction::Down:
+    case Arrow::Direction::Up:
+        return { longestLineWidth + (labelPadding * 2), totalTextHeight + (labelPadding * 2) + labelArrowSize };
+    case Arrow::Direction::Right:
+    case Arrow::Direction::Left:
+        return { longestLineWidth + (labelPadding * 2) + labelArrowSize, totalTextHeight + (labelPadding * 2) };
+    case Arrow::Direction::None:
+        return { longestLineWidth + (labelPadding * 2), totalTextHeight + (labelPadding * 2) };
+    }
+
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
+FloatSize InspectorOverlayLabel::expectedSize(const String& text, Arrow::Direction direction)
+{
+    return InspectorOverlayLabel::expectedSize({ { text, Color::black } }, direction);
+}
+
+} // namespace WebCore

Added: trunk/Source/WebCore/inspector/InspectorOverlayLabel.h (0 => 289769)


--- trunk/Source/WebCore/inspector/InspectorOverlayLabel.h	                        (rev 0)
+++ trunk/Source/WebCore/inspector/InspectorOverlayLabel.h	2022-02-14 22:59:12 UTC (rev 289769)
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2022 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Color.h"
+#include "FloatPoint.h"
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class FloatSize;
+class GraphicsContext;
+class Path;
+
+class InspectorOverlayLabel {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    struct Arrow {
+        WTF_MAKE_STRUCT_FAST_ALLOCATED;
+
+        enum class Direction : uint8_t {
+            None,
+            Down,
+            Up,
+            Left,
+            Right,
+        };
+
+        enum class Alignment : uint8_t {
+            None,
+            Leading, // Positioned at the left/top side of edge.
+            Middle, // Positioned at the center on the edge.
+            Trailing, // Positioned at the right/bottom side of the edge.
+        };
+
+        Direction direction;
+        Alignment alignment;
+
+        Arrow(Direction direction, Alignment alignment)
+            : direction(direction)
+            , alignment(alignment)
+        {
+            ASSERT(alignment != Alignment::None || direction == Direction::None);
+        }
+
+#if PLATFORM(IOS_FAMILY)
+        template<class Encoder> void encode(Encoder&) const;
+        template<class Decoder> static std::optional<InspectorOverlayLabel::Arrow> decode(Decoder&);
+#endif
+    };
+
+    struct Content {
+        WTF_MAKE_STRUCT_FAST_ALLOCATED;
+
+        String text;
+        Color textColor;
+
+#if PLATFORM(IOS_FAMILY)
+        template<class Encoder> void encode(Encoder&) const;
+        template<class Decoder> static std::optional<InspectorOverlayLabel::Content> decode(Decoder&);
+#endif
+    };
+
+    WEBCORE_EXPORT InspectorOverlayLabel(Vector<Content>&&, FloatPoint, Color backgroundColor, Arrow);
+    InspectorOverlayLabel(const String&, FloatPoint, Color backgroundColor, Arrow);
+
+    Path draw(GraphicsContext&, float maximumLineWidth = 0);
+
+    static FloatSize expectedSize(const Vector<Content>&, Arrow::Direction);
+    static FloatSize expectedSize(const String&, Arrow::Direction);
+
+#if PLATFORM(IOS_FAMILY)
+    template<class Encoder> void encode(Encoder&) const;
+    template<class Decoder> static std::optional<InspectorOverlayLabel> decode(Decoder&);
+#endif
+
+private:
+    Vector<Content> m_contents;
+    FloatPoint m_location;
+    Color m_backgroundColor;
+    Arrow m_arrow;
+};
+
+#if PLATFORM(IOS_FAMILY)
+
+template<class Encoder> void InspectorOverlayLabel::encode(Encoder& encoder) const
+{
+    encoder << m_contents;
+    encoder << m_location;
+    encoder << m_backgroundColor;
+    encoder << m_arrow;
+}
+
+template<class Decoder> std::optional<InspectorOverlayLabel> InspectorOverlayLabel::decode(Decoder& decoder)
+{
+    std::optional<Vector<Content>> contents;
+    decoder >> contents;
+    if (!contents)
+        return std::nullopt;
+
+    std::optional<FloatPoint> location;
+    decoder >> location;
+    if (!location)
+        return std::nullopt;
+
+    std::optional<Color> backgroundColor;
+    decoder >> backgroundColor;
+    if (!backgroundColor)
+        return std::nullopt;
+
+    std::optional<Arrow> arrow;
+    decoder >> arrow;
+    if (!arrow)
+        return std::nullopt;
+
+    return { {
+        WTFMove(*contents),
+        *location,
+        *backgroundColor,
+        *arrow
+    } };
+}
+
+template<class Encoder> void InspectorOverlayLabel::Arrow::encode(Encoder& encoder) const
+{
+    encoder << direction;
+    encoder << alignment;
+}
+
+template<class Decoder> std::optional<InspectorOverlayLabel::Arrow> InspectorOverlayLabel::Arrow::decode(Decoder& decoder)
+{
+    std::optional<Direction> direction;
+    decoder >> direction;
+    if (!direction)
+        return std::nullopt;
+
+    std::optional<Alignment> alignment;
+    decoder >> alignment;
+    if (!alignment)
+        return std::nullopt;
+
+    return { { *direction, *alignment } };
+}
+
+template<class Encoder> void InspectorOverlayLabel::Content::encode(Encoder& encoder) const
+{
+    encoder << text;
+    encoder << textColor;
+}
+
+template<class Decoder> std::optional<InspectorOverlayLabel::Content> InspectorOverlayLabel::Content::decode(Decoder& decoder)
+{
+    std::optional<String> text;
+    decoder >> text;
+    if (!text)
+        return std::nullopt;
+
+    std::optional<Color> textColor;
+    decoder >> textColor;
+    if (!textColor)
+        return std::nullopt;
+
+    return { { *text, *textColor } };
+}
+
+#endif
+
+} // namespace WebCore
+
+namespace WTF {
+
+template<> struct EnumTraits<WebCore::InspectorOverlayLabel::Arrow::Direction> {
+    using values = EnumValues<
+        WebCore::InspectorOverlayLabel::Arrow::Direction,
+        WebCore::InspectorOverlayLabel::Arrow::Direction::None,
+        WebCore::InspectorOverlayLabel::Arrow::Direction::Down,
+        WebCore::InspectorOverlayLabel::Arrow::Direction::Up,
+        WebCore::InspectorOverlayLabel::Arrow::Direction::Left,
+        WebCore::InspectorOverlayLabel::Arrow::Direction::Right
+    >;
+};
+
+template<> struct EnumTraits<WebCore::InspectorOverlayLabel::Arrow::Alignment> {
+    using values = EnumValues<
+        WebCore::InspectorOverlayLabel::Arrow::Alignment,
+        WebCore::InspectorOverlayLabel::Arrow::Alignment::None,
+        WebCore::InspectorOverlayLabel::Arrow::Alignment::Leading,
+        WebCore::InspectorOverlayLabel::Arrow::Alignment::Middle,
+        WebCore::InspectorOverlayLabel::Arrow::Alignment::Trailing
+    >;
+};
+
+}
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to