Commit d9569a65 authored by Fredrik Söderquist's avatar Fredrik Söderquist Committed by Commit Bot

Fix bounding box calculation for <tspan> (and other LayoutSVGInlines)

Make the various bounding box calculations for LayoutSVGInline return
values derived from the associated flow boxes rather than delegating
to the ancestor LayoutSVGText. Refactor and reuse the code from the
similar functionality LayoutSVGText.

Since the reference box for these element should still remain the same
as the reference box for the ancestor <text> add a new helper to
compute (delegate) that and use that when resolving clips/filter/et.c.

Bug: 951706, 349835, 267481
Change-Id: Idf4a4c586ef9902cac2e795e9fbda0009b2ea0ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2088094
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Reviewed-by: default avatarStephen Chenney <schenney@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748175}
parent 8d36b2d6
......@@ -60,27 +60,27 @@ InlineFlowBox* LayoutSVGInline::CreateInlineFlowBox() {
}
FloatRect LayoutSVGInline::ObjectBoundingBox() const {
if (const LayoutSVGText* text_root =
LayoutSVGText::LocateLayoutSVGTextAncestor(this))
return text_root->ObjectBoundingBox();
return FloatRect();
FloatRect bounds;
for (InlineFlowBox* box : *LineBoxes())
bounds.Unite(FloatRect(box->FrameRect()));
return bounds;
}
FloatRect LayoutSVGInline::StrokeBoundingBox() const {
if (const LayoutSVGText* text_root =
LayoutSVGText::LocateLayoutSVGTextAncestor(this))
return text_root->StrokeBoundingBox();
if (!FirstLineBox())
return FloatRect();
return SVGLayoutSupport::ExtendTextBBoxWithStroke(*this, ObjectBoundingBox());
}
FloatRect LayoutSVGInline::VisualRectInLocalSVGCoordinates() const {
if (const LayoutSVGText* text_root =
LayoutSVGText::LocateLayoutSVGTextAncestor(this))
return text_root->VisualRectInLocalSVGCoordinates();
if (!FirstLineBox())
return FloatRect();
const LayoutSVGText* text_root =
LayoutSVGText::LocateLayoutSVGTextAncestor(this);
if (!text_root)
return FloatRect();
return SVGLayoutSupport::ComputeVisualRectForText(
*this, ObjectBoundingBox(), text_root->ObjectBoundingBox());
}
PhysicalRect LayoutSVGInline::VisualRectInDocument(
......@@ -103,18 +103,10 @@ const LayoutObject* LayoutSVGInline::PushMappingToContainer(
void LayoutSVGInline::AbsoluteQuads(Vector<FloatQuad>& quads,
MapCoordinatesFlags mode) const {
const LayoutSVGText* text_root =
LayoutSVGText::LocateLayoutSVGTextAncestor(this);
if (!text_root)
return;
FloatRect text_bounding_box = text_root->StrokeBoundingBox();
for (InlineFlowBox* box : *LineBoxes()) {
FloatRect box_rect(box->FrameRect());
quads.push_back(LocalToAbsoluteQuad(
FloatRect(text_bounding_box.X() + box->X().ToFloat(),
text_bounding_box.Y() + box->Y().ToFloat(),
box->Width().ToFloat(), box->Height().ToFloat()),
mode));
SVGLayoutSupport::ExtendTextBBoxWithStroke(*this, box_rect), mode));
}
}
......
......@@ -38,11 +38,6 @@ class LayoutSVGInline : public LayoutInline {
bool IsChildAllowed(LayoutObject*, const ComputedStyle&) const override;
// Chapter 10.4 of the SVG Specification say that we should use the
// object bounding box of the parent text element.
// We search for the root text element and take its bounding box.
// It is also necessary to take the stroke and visual rect of this element,
// since we need it for filters.
FloatRect ObjectBoundingBox() const final;
FloatRect StrokeBoundingBox() const final;
FloatRect VisualRectInLocalSVGCoordinates() const final;
......
......@@ -147,7 +147,7 @@ SVGPaintServer SVGPaintServer::RequestForLayoutObject(
return SVGPaintServer(paint_description.color);
SVGPaintServer paint_server = paint_description.resource->PreparePaintServer(
*SVGResources::GetClient(layout_object),
layout_object.ObjectBoundingBox());
SVGResources::ReferenceBoxForEffects(layout_object));
if (paint_server.IsValid())
return paint_server;
if (paint_description.has_fallback)
......
......@@ -367,27 +367,17 @@ FloatRect LayoutSVGText::ObjectBoundingBox() const {
}
FloatRect LayoutSVGText::StrokeBoundingBox() const {
FloatRect stroke_boundaries = ObjectBoundingBox();
const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
if (!svg_style.HasStroke())
return stroke_boundaries;
DCHECK(GetElement());
SVGLengthContext length_context(GetElement());
stroke_boundaries.Inflate(
length_context.ValueForLength(svg_style.StrokeWidth()));
return stroke_boundaries;
if (!FirstRootBox())
return FloatRect();
return SVGLayoutSupport::ExtendTextBBoxWithStroke(*this, ObjectBoundingBox());
}
FloatRect LayoutSVGText::VisualRectInLocalSVGCoordinates() const {
FloatRect visual_rect = StrokeBoundingBox();
SVGLayoutSupport::AdjustVisualRectWithResources(*this, ObjectBoundingBox(),
visual_rect);
if (const ShadowList* text_shadow = StyleRef().TextShadow())
text_shadow->AdjustRectForShadow(visual_rect);
return visual_rect;
if (!FirstRootBox())
return FloatRect();
const FloatRect object_bounds = ObjectBoundingBox();
return SVGLayoutSupport::ComputeVisualRectForText(*this, object_bounds,
object_bounds);
}
void LayoutSVGText::AddOutlineRects(Vector<PhysicalRect>& rects,
......
......@@ -409,6 +409,34 @@ void SVGLayoutSupport::AdjustVisualRectWithResources(
visual_rect.Intersect(masker->ResourceBoundingBox(object_bounding_box));
}
FloatRect SVGLayoutSupport::ExtendTextBBoxWithStroke(
const LayoutObject& layout_object,
const FloatRect& text_bounds) {
DCHECK(layout_object.IsSVGText() || layout_object.IsSVGInline());
FloatRect bounds = text_bounds;
const SVGComputedStyle& svg_style = layout_object.StyleRef().SvgStyle();
if (svg_style.HasStroke()) {
SVGLengthContext length_context(To<SVGElement>(layout_object.GetNode()));
// TODO(fs): This approximation doesn't appear to be conservative enough
// since while text (usually?) won't have caps it could have joins and thus
// miters.
bounds.Inflate(length_context.ValueForLength(svg_style.StrokeWidth()));
}
return bounds;
}
FloatRect SVGLayoutSupport::ComputeVisualRectForText(
const LayoutObject& layout_object,
const FloatRect& text_bounds,
const FloatRect& reference_box) {
DCHECK(layout_object.IsSVGText() || layout_object.IsSVGInline());
FloatRect visual_rect = ExtendTextBBoxWithStroke(layout_object, text_bounds);
if (const ShadowList* text_shadow = layout_object.StyleRef().TextShadow())
text_shadow->AdjustRectForShadow(visual_rect);
AdjustVisualRectWithResources(layout_object, reference_box, visual_rect);
return visual_rect;
}
bool SVGLayoutSupport::HasFilterResource(const LayoutObject& object) {
SVGResources* resources =
SVGResourcesCache::CachedResourcesForLayoutObject(object);
......
......@@ -68,6 +68,15 @@ class CORE_EXPORT SVGLayoutSupport {
const FloatRect& object_bounding_box,
FloatRect&);
// Add any contribution from 'stroke' to a text content bounding rect.
static FloatRect ExtendTextBBoxWithStroke(const LayoutObject&,
const FloatRect& text_bounds);
// Compute the visual rect for the a text content LayoutObject.
static FloatRect ComputeVisualRectForText(const LayoutObject&,
const FloatRect& text_bounds,
const FloatRect& reference_box);
// Determine if the LayoutObject references a filter resource object.
static bool HasFilterResource(const LayoutObject&);
......
......@@ -27,6 +27,7 @@
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_marker.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_text.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
......@@ -51,6 +52,20 @@ SVGElementResourceClient* SVGResources::GetClient(const LayoutObject& object) {
return To<SVGElement>(object.GetNode())->GetSVGResourceClient();
}
FloatRect SVGResources::ReferenceBoxForEffects(
const LayoutObject& layout_object) {
// Text "sub-elements" (<tspan>, <textpath>, <a>) should use the entire
// <text>s object bounding box rather then their own.
// https://svgwg.org/svg2-draft/text.html#ObjectBoundingBoxUnitsTextObjects
const LayoutObject* obb_layout_object = &layout_object;
if (layout_object.IsSVGInline()) {
obb_layout_object =
LayoutSVGText::LocateLayoutSVGTextAncestor(&layout_object);
}
DCHECK(obb_layout_object);
return obb_layout_object->ObjectBoundingBox();
}
static HashSet<AtomicString>& ClipperFilterMaskerTags() {
DEFINE_STATIC_LOCAL(
HashSet<AtomicString>, tag_list,
......
......@@ -50,6 +50,7 @@ class SVGResources {
SVGResources();
static SVGElementResourceClient* GetClient(const LayoutObject&);
static FloatRect ReferenceBoxForEffects(const LayoutObject&);
static std::unique_ptr<SVGResources> BuildResources(const LayoutObject&,
const ComputedStyle&);
......
......@@ -65,7 +65,7 @@ LayoutSVGResourceClipper* ResolveElementReference(
FloatRect ClipPathClipper::LocalReferenceBox(const LayoutObject& object) {
if (object.IsSVGChild())
return object.ObjectBoundingBox();
return SVGResources::ReferenceBoxForEffects(object);
if (object.IsBox())
return FloatRect(ToLayoutBox(object).BorderBoxRect());
......
......@@ -23,8 +23,9 @@ base::Optional<IntRect> CSSMaskPainter::MaskBoundingBox(
SVGResourcesCache::CachedResourcesForLayoutObject(object);
LayoutSVGResourceMasker* masker = resources ? resources->Masker() : nullptr;
if (masker) {
return EnclosingIntRect(
masker->ResourceBoundingBox(object.ObjectBoundingBox()));
const FloatRect reference_box =
SVGResources::ReferenceBoxForEffects(object);
return EnclosingIntRect(masker->ResourceBoundingBox(reference_box));
}
}
......
......@@ -101,7 +101,7 @@ GraphicsContext* SVGFilterPainter::PrepareEffect(
}
auto* node_map = MakeGarbageCollected<SVGFilterGraphNodeMap>();
FilterEffectBuilder builder(object.ObjectBoundingBox(), 1);
FilterEffectBuilder builder(SVGResources::ReferenceBoxForEffects(object), 1);
Filter* filter = builder.BuildReferenceFilter(
To<SVGFilterElement>(*filter_.GetElement()), nullptr, node_map);
if (!filter || !filter->LastEffect())
......
......@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/paint/svg_mask_painter.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
......@@ -41,7 +42,8 @@ void SVGMaskPainter::FinishEffect(const LayoutObject& object,
AffineTransform content_transformation;
sk_sp<const PaintRecord> record = mask_.CreatePaintRecord(
content_transformation, object.ObjectBoundingBox(), context);
content_transformation, SVGResources::ReferenceBoxForEffects(object),
context);
if (DrawingRecorder::UseCachedDrawingIfPossible(context, display_item_client,
DisplayItem::kSVGMask))
......
<!doctype html>
<title>{Element,Range}.prototype.getBoundingClientRect on SVG &lt;tspan&gt;</title>
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-getboundingclientrect">
<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-range-getboundingclientrect">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<svg>
<text y="180" font-size="100" font-family="Ahem"
fill="lightblue">X<tspan fill="blue">XX</tspan></text>
</svg>
<script>
function check(object) {
let rect = object.getBoundingClientRect();
assert_equals(rect.left, 108, 'left');
assert_equals(rect.top, 108, 'top');
assert_equals(rect.width, 200, 'width');
assert_equals(rect.height, 100, 'height');
}
async_test(t => {
window.addEventListener("load", t.step_func_done(() => {
let tspan = document.querySelector('tspan');
check(tspan);
}));
}, document.title + ', Element');
async_test(t => {
window.addEventListener("load", t.step_func_done(() => {
let tspan = document.querySelector('tspan');
let range = new Range();
range.selectNode(tspan);
check(range);
}));
}, document.title + ', Range');
</script>
<!DOCTYPE html>
<title>getBBox tests</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<!doctype html>
<title>SVGGraphicsElement.prototype.getBBox</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="testcontainer">
<svg width="1" height="1" visibility="hidden">
<g id="g1">
......@@ -126,5 +126,4 @@ test(function() {
let r13 = document.getElementById("r13");
assert_rect_approx_equals(g11.getBBox(), r13.getBBox(), EPSILON);
}, "empty image does not contribute to parent bbox");
</script>
<!doctype html>
<title>SVGGraphicsElement.prototype.getBBox on &lt;tspan&gt;</title>
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<svg height="100">
<text y="180" font-size="100" font-family="Ahem" fill="lightblue"
transform="translate(0, -100)">X<tspan fill="blue">X</tspan></text>
</svg>
<script>
async_test(t => {
onload = t.step_func_done(() => {
let tspan = document.querySelector('tspan');
let bbox = tspan.getBBox();
for (let property of ['x', 'y', 'width', 'height'])
assert_equals(bbox[property], 100, property);
});
});
</script>
......@@ -27,39 +27,39 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"object": "InlineTextBox 'PASS'",
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "appeared"
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'SS'",
"rect": [10, 60, 310, 380],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 150, 140],
"reason": "disappeared"
}
]
}
......
......@@ -26,34 +26,34 @@
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "appeared"
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 150, 140],
"reason": "disappeared"
}
]
......
......@@ -17,34 +17,34 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"object": "InlineTextBox ' '",
"rect": [10, 180, 310, 260],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox ' '",
"rect": [10, 180, 310, 260],
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 310, 260],
"reason": "appeared"
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 310, 260],
"reason": "appeared"
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 260, 140],
"reason": "disappeared"
}
]
}
......
......@@ -22,28 +22,28 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 310, 260],
"reason": "disappeared"
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"reason": "appeared"
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"rect": [10, 60, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 260, 140],
"reason": "disappeared"
}
]
......
......@@ -12,13 +12,13 @@
"reason": "subtree"
},
{
"object": "InlineTextBox 'Y'",
"object": "InlineTextBox 'Z'",
"rect": [8, 8, 300, 100],
"reason": "subtree"
},
{
"object": "InlineTextBox 'Z'",
"rect": [8, 8, 300, 100],
"object": "InlineTextBox 'Y'",
"rect": [108, 8, 100, 100],
"reason": "subtree"
}
]
......
......@@ -8,7 +8,7 @@
"paintInvalidations": [
{
"object": "InlineTextBox 'Y'",
"rect": [8, 8, 300, 100],
"rect": [108, 8, 100, 100],
"reason": "subtree"
}
]
......
......@@ -27,39 +27,39 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"object": "InlineTextBox 'PASS'",
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "appeared"
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'SS'",
"rect": [10, 60, 310, 380],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 150, 140],
"reason": "disappeared"
}
]
}
......
......@@ -26,34 +26,34 @@
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "appeared"
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PA'",
"rect": [10, 60, 150, 140],
"reason": "disappeared"
}
]
......
......@@ -17,34 +17,34 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"object": "InlineTextBox ' '",
"rect": [10, 180, 310, 260],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"reason": "disappeared"
"rect": [10, 300, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 380],
"rect": [10, 300, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox ' '",
"rect": [10, 180, 310, 260],
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 310, 260],
"reason": "appeared"
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 310, 260],
"reason": "appeared"
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 260, 140],
"reason": "disappeared"
}
]
}
......
......@@ -22,28 +22,28 @@
"reason": "disappeared"
},
{
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 310, 260],
"reason": "disappeared"
"object": "InlineTextBox 'PASS'",
"rect": [10, 180, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"reason": "appeared"
"rect": [10, 180, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"rect": [10, 60, 280, 140],
"reason": "appeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"rect": [10, 60, 280, 140],
"reason": "disappeared"
},
{
"object": "InlineTextBox 'PASS'",
"rect": [10, 60, 310, 260],
"object": "InlineTextBox 'FAIL'",
"rect": [10, 60, 260, 140],
"reason": "disappeared"
}
]
......
......@@ -12,13 +12,13 @@
"reason": "subtree"
},
{
"object": "InlineTextBox 'Y'",
"object": "InlineTextBox 'Z'",
"rect": [8, 8, 300, 100],
"reason": "subtree"
},
{
"object": "InlineTextBox 'Z'",
"rect": [8, 8, 300, 100],
"object": "InlineTextBox 'Y'",
"rect": [108, 8, 100, 100],
"reason": "subtree"
}
]
......
......@@ -8,7 +8,7 @@
"paintInvalidations": [
{
"object": "InlineTextBox 'Y'",
"rect": [8, 8, 300, 100],
"rect": [108, 8, 100, 100],
"reason": "subtree"
}
]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment