From 41c3e58e272ade0636e73ea63dec787ff7b052d4 Mon Sep 17 00:00:00 2001 From: Hixie Date: Wed, 24 Jun 2015 17:01:14 -0700 Subject: [PATCH] Use the baseline information exposed by C++ to pipe baseline data through RenderBox. This also fixes the C++ side to give the right baseline information. Previously it was giving the baseline distance for the font, but not for the actual laid-out text. I considered also providing a "defaultBaseline" accessor that returns the distance for the actual dominant baseline, but it turns out right now we never decide the baseline is ideographic. We always use the alphabetic baseline. We should probably fix that... R=eseidel@chromium.org Review URL: https://codereview.chromium.org/1200233002. --- engine/core/dom/Element.cpp | 4 +- engine/core/rendering/RenderBlock.cpp | 4 +- engine/core/rendering/RenderBlock.h | 2 +- engine/core/rendering/RenderBox.h | 17 ++++- engine/core/rendering/RenderFlexibleBox.cpp | 10 +-- engine/core/rendering/RenderFlexibleBox.h | 2 +- engine/core/rendering/RenderParagraph.cpp | 11 ++- engine/core/rendering/RenderParagraph.h | 2 +- examples/rendering/baseline.dart | 78 ++++++++++++++++++++ sdk/lib/rendering/block.dart | 4 ++ sdk/lib/rendering/box.dart | 79 +++++++++++++++++++++ sdk/lib/rendering/flex.dart | 13 ++++ sdk/lib/rendering/paragraph.dart | 10 +++ sdk/lib/rendering/stack.dart | 4 ++ 14 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 examples/rendering/baseline.dart diff --git a/engine/core/dom/Element.cpp b/engine/core/dom/Element.cpp index 367843c5dd2..02afe79cc17 100644 --- a/engine/core/dom/Element.cpp +++ b/engine/core/dom/Element.cpp @@ -975,14 +975,14 @@ void Element::setMaxContentWidth(double width) double Element::alphabeticBaseline() const { if (RenderBox* box = renderBox()) - return box->baselinePosition(AlphabeticBaseline, true, HorizontalLine, PositionOfInteriorLineBoxes); + return box->firstLineBoxBaseline(FontBaselineOrAuto(AlphabeticBaseline)); return 0; } double Element::ideographicBaseline() const { if (RenderBox* box = renderBox()) - return box->baselinePosition(IdeographicBaseline, true, HorizontalLine, PositionOfInteriorLineBoxes); + return box->firstLineBoxBaseline(FontBaselineOrAuto(IdeographicBaseline)); return 0; } diff --git a/engine/core/rendering/RenderBlock.cpp b/engine/core/rendering/RenderBlock.cpp index af07da2b312..85e57b3c85c 100644 --- a/engine/core/rendering/RenderBlock.cpp +++ b/engine/core/rendering/RenderBlock.cpp @@ -1351,11 +1351,11 @@ LayoutUnit RenderBlock::minLineHeightForReplacedRenderer(bool isFirstLine, Layou return std::max(replacedHeight, lineHeight(isFirstLine, HorizontalLine, PositionOfInteriorLineBoxes)); } -int RenderBlock::firstLineBoxBaseline() const +int RenderBlock::firstLineBoxBaseline(FontBaselineOrAuto baselineType) const { for (RenderBox* curr = firstChildBox(); curr; curr = curr->nextSiblingBox()) { if (!curr->isFloatingOrOutOfFlowPositioned()) { - int result = curr->firstLineBoxBaseline(); + int result = curr->firstLineBoxBaseline(baselineType); if (result != -1) return curr->logicalTop() + result; // Translate to our coordinate space. } diff --git a/engine/core/rendering/RenderBlock.h b/engine/core/rendering/RenderBlock.h index bac68462e7b..44bf5896ac3 100644 --- a/engine/core/rendering/RenderBlock.h +++ b/engine/core/rendering/RenderBlock.h @@ -216,7 +216,7 @@ protected: virtual void computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const override; virtual void computePreferredLogicalWidths() override; - virtual int firstLineBoxBaseline() const override; + virtual int firstLineBoxBaseline(FontBaselineOrAuto baselineType) const override; virtual int inlineBlockBaseline(LineDirectionMode) const override; virtual int lastLineBoxBaseline(LineDirectionMode) const; diff --git a/engine/core/rendering/RenderBox.h b/engine/core/rendering/RenderBox.h index 2304e9b79ef..5d27682ba3b 100644 --- a/engine/core/rendering/RenderBox.h +++ b/engine/core/rendering/RenderBox.h @@ -66,6 +66,21 @@ enum LayerType { OverflowClipLayer, }; +struct FontBaselineOrAuto { + FontBaselineOrAuto() + : m_auto(true) + , m_baseline(AlphabeticBaseline) + { + } + FontBaselineOrAuto(FontBaseline baseline) + : m_auto(false) + , m_baseline(baseline) + { + } + bool m_auto; + FontBaseline m_baseline; +}; + class RenderBox : public RenderBoxModelObject { public: explicit RenderBox(ContainerNode*); @@ -429,7 +444,7 @@ public: RenderLayer* enclosingFloatPaintingLayer() const; - virtual int firstLineBoxBaseline() const { return -1; } + virtual int firstLineBoxBaseline(FontBaselineOrAuto baselineType) const { return -1; } virtual int inlineBlockBaseline(LineDirectionMode) const { return -1; } // Returns -1 if we should skip this box when computing the baseline of an inline-block. bool isFlexItem() const { return !isInline() && !isFloatingOrOutOfFlowPositioned() && parent() && parent()->isFlexibleBox(); } diff --git a/engine/core/rendering/RenderFlexibleBox.cpp b/engine/core/rendering/RenderFlexibleBox.cpp index bfd8e9775d3..629f1c001c9 100644 --- a/engine/core/rendering/RenderFlexibleBox.cpp +++ b/engine/core/rendering/RenderFlexibleBox.cpp @@ -119,14 +119,14 @@ static int synthesizedBaselineFromContentBox(const RenderBox* box, LineDirection int RenderFlexibleBox::baselinePosition(FontBaseline, bool, LineDirectionMode direction, LinePositionMode mode) const { ASSERT(mode == PositionOnContainingLine); - int baseline = firstLineBoxBaseline(); + int baseline = firstLineBoxBaseline(FontBaselineOrAuto()); if (baseline == -1) baseline = synthesizedBaselineFromContentBox(this, direction); return beforeMarginInLineDirection(direction) + baseline; } -int RenderFlexibleBox::firstLineBoxBaseline() const +int RenderFlexibleBox::firstLineBoxBaseline(FontBaselineOrAuto baselineType) const { if (m_numberOfInFlowChildrenOnFirstLine <= 0) return -1; @@ -155,7 +155,7 @@ int RenderFlexibleBox::firstLineBoxBaseline() const if (isColumnFlow() && !hasOrthogonalFlow(baselineChild)) return mainAxisExtentForChild(baselineChild) + baselineChild->logicalTop(); - int baseline = baselineChild->firstLineBoxBaseline(); + int baseline = baselineChild->firstLineBoxBaseline(baselineType); if (baseline == -1) { // FIXME: We should pass |direction| into firstLineBoxBaseline and stop bailing out if we're a writing mode root. // This would also fix some cases where the flexbox is orthogonal to its container. @@ -168,7 +168,7 @@ int RenderFlexibleBox::firstLineBoxBaseline() const int RenderFlexibleBox::inlineBlockBaseline(LineDirectionMode direction) const { - int baseline = firstLineBoxBaseline(); + int baseline = firstLineBoxBaseline(FontBaselineOrAuto()); if (baseline != -1) return baseline; @@ -680,7 +680,7 @@ bool RenderFlexibleBox::updateAutoMarginsInCrossAxis(RenderBox* child, LayoutUni LayoutUnit RenderFlexibleBox::marginBoxAscentForChild(RenderBox* child) { - LayoutUnit ascent = child->firstLineBoxBaseline(); + LayoutUnit ascent = child->firstLineBoxBaseline(FontBaselineOrAuto()); if (ascent == -1) ascent = crossAxisExtentForChild(child); return ascent + flowAwareMarginBeforeForChild(child); diff --git a/engine/core/rendering/RenderFlexibleBox.h b/engine/core/rendering/RenderFlexibleBox.h index 9d935007d09..0bca6796ad3 100644 --- a/engine/core/rendering/RenderFlexibleBox.h +++ b/engine/core/rendering/RenderFlexibleBox.h @@ -47,7 +47,7 @@ public: void layout(); virtual int baselinePosition(FontBaseline, bool firstLine, LineDirectionMode, LinePositionMode = PositionOnContainingLine) const override; - virtual int firstLineBoxBaseline() const override; + virtual int firstLineBoxBaseline(FontBaselineOrAuto baselineType) const override; virtual int inlineBlockBaseline(LineDirectionMode) const override; virtual void paintChildren(PaintInfo&, const LayoutPoint&, Vector& layers) override final; diff --git a/engine/core/rendering/RenderParagraph.cpp b/engine/core/rendering/RenderParagraph.cpp index cde5a014399..5b2457ea4ed 100644 --- a/engine/core/rendering/RenderParagraph.cpp +++ b/engine/core/rendering/RenderParagraph.cpp @@ -1356,9 +1356,16 @@ void RenderParagraph::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); } -int RenderParagraph::firstLineBoxBaseline() const +int RenderParagraph::firstLineBoxBaseline(FontBaselineOrAuto baselineType) const { - return firstLineBox() ? firstLineBox()->logicalTop() + style(true)->fontMetrics().ascent(firstRootBox()->baselineType()) : -1; + if (!firstLineBox()) + return -1; + FontBaseline baseline; + if (baselineType.m_auto) + baseline = firstRootBox()->baselineType(); + else + baseline = baselineType.m_baseline; + return firstLineBox()->logicalTop() + style(true)->fontMetrics().ascent(baseline); } int RenderParagraph::lastLineBoxBaseline(LineDirectionMode lineDirection) const diff --git a/engine/core/rendering/RenderParagraph.h b/engine/core/rendering/RenderParagraph.h index d7015634ba4..3d942c73199 100644 --- a/engine/core/rendering/RenderParagraph.h +++ b/engine/core/rendering/RenderParagraph.h @@ -74,7 +74,7 @@ protected: void computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const final; - int firstLineBoxBaseline() const final; + int firstLineBoxBaseline(FontBaselineOrAuto baselineType) const final; int lastLineBoxBaseline(LineDirectionMode) const final; private: diff --git a/examples/rendering/baseline.dart b/examples/rendering/baseline.dart new file mode 100644 index 00000000000..2c52239597d --- /dev/null +++ b/examples/rendering/baseline.dart @@ -0,0 +1,78 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:sky' as sky; + +import 'package:sky/painting/text_style.dart'; +import 'package:sky/rendering/block.dart'; +import 'package:sky/rendering/box.dart'; +import 'package:sky/rendering/object.dart'; +import 'package:sky/rendering/paragraph.dart'; +import 'package:sky/rendering/sky_binding.dart'; + +RenderBox getBox(double lh) { + RenderParagraph paragraph = new RenderParagraph( + new InlineStyle( + new TextStyle(), + [ + new InlineText('test'), + new InlineStyle( + new TextStyle( + color: const Color(0xFF0000A0), + fontFamily: 'serif', + fontSize: 50.0, + height: lh + ), + [new InlineText('مرحبا Hello')] + ) + ] + ) + ); + return new RenderPadding( + padding: new EdgeDims.all(10.0), + child: new RenderConstrainedBox( + additionalConstraints: new BoxConstraints.tightFor(height: 200.0), + child: new RenderDecoratedBox( + decoration: new BoxDecoration( + backgroundColor: const Color(0xFFFFFFFF) + ), + child: new RenderPadding( + padding: new EdgeDims.all(10.0), + child: new RenderCustomPaint( + child: paragraph, + callback: (canvas, size) { + double baseline = paragraph.getDistanceToBaseline(TextBaseline.alphabetic); + double w = paragraph.getMaxIntrinsicWidth(new BoxConstraints.loose(size)); + double h = paragraph.getMaxIntrinsicHeight(new BoxConstraints.loose(size)); + Path path = new Path(); + path.moveTo(0.0, 0.0); + path.lineTo(w, 0.0); + path.moveTo(0.0, baseline); + path.lineTo(w, baseline); + path.moveTo(0.0, h); + path.lineTo(w, h); + Paint paint = new Paint(); + paint.color = const Color(0xFFFF9000); + paint.setStyle(sky.PaintingStyle.stroke); + paint.strokeWidth = 3.0; + canvas.drawPath(path, paint); + } + ) + ) + ) + ) + ); +} + +void main() { + RenderBox root = new RenderBlock(children: [ + new RenderConstrainedBox( + additionalConstraints: new BoxConstraints.tightFor(height: 50.0) + ), + getBox(1.0), + getBox(null), + ]); + var b = new SkyBinding(root: root); + // b.onFrame = b.debugDumpRenderTree; +} diff --git a/sdk/lib/rendering/block.dart b/sdk/lib/rendering/block.dart index 4d163db4a29..ff840446b1a 100644 --- a/sdk/lib/rendering/block.dart +++ b/sdk/lib/rendering/block.dart @@ -77,6 +77,10 @@ class RenderBlock extends RenderBox with ContainerRenderObjectMixin 'position=$position'; } +enum TextBaseline { alphabetic, ideographic } + abstract class RenderBox extends RenderObject { void setParentData(RenderObject child) { @@ -259,6 +261,31 @@ abstract class RenderBox extends RenderObject { return constraints.constrainHeight(0.0); } + // getDistanceToBaseline() should return the distance from the + // y-coordinate of the position of the box to the y-coordinate of + // the first given baseline in the box's contents. This is used by + // certain layout models to align adjacent boxes on a common + // baseline, regardless of padding, font size differences, etc. If + // there is no baseline, then it should return the distance from the + // y-coordinate of the position of the box to the y-coordinate of + // the bottom of the box, i.e., the height of the box. + // Only call this after layout has been performed. + double getDistanceToBaseline(TextBaseline baseline) { + assert(!needsLayout); + double result = getDistanceToActualBaseline(baseline); + if (result == null) + return size.height; + return result; + } + // getDistanceToActualBaseline() should return the distance from the + // y-coordinate of the position of the box to the y-coordinate of + // the first given baseline in the box's contents, if any, or null + // otherwise. + double getDistanceToActualBaseline(TextBaseline baseline) { + assert(!needsLayout); + return null; + } + // This whole block should only be here in debug builds bool _debugDoingThisLayout = false; bool _debugCanParentUseSize; @@ -348,6 +375,12 @@ class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin // DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS abstract class RenderBoxContainerDefaultsMixin> implements ContainerRenderObjectMixin { + double defaultGetDistanceToFirstActualBaseline(TextBaseline baseline) { + assert(!needsLayout); + RenderBox child = firstChild; + while (child != null) { + assert(child.parentData is ParentDataType); + double result = child.getDistanceToActualBaseline(baseline); + if (result != null) + return result + child.parentData.position.y; + child = child.parentData.nextSibling; + } + return null; + } + + double defaultGetDistanceToHighestActualBaseline(TextBaseline baseline) { + assert(!needsLayout); + double result; + RenderBox child = firstChild; + while (child != null) { + assert(child.parentData is ParentDataType); + double candidate = child.getDistanceToActualBaseline(baseline); + if (candidate != null) { + candidate += child.parentData.position.x; + if (result != null) + result = math.min(result, candidate); + else + result = candidate; + } + child = child.parentData.nextSibling; + } + return result; + } + void defaultHitTestChildren(HitTestResult result, { Point position }) { // the x, y parameters have the top left of the node's box as the origin ChildType child = lastChild; diff --git a/sdk/lib/rendering/flex.dart b/sdk/lib/rendering/flex.dart index e883e51612b..4eee1bd854f 100644 --- a/sdk/lib/rendering/flex.dart +++ b/sdk/lib/rendering/flex.dart @@ -110,6 +110,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin c.getMaxIntrinsicHeight(innerConstraints)); } + double getDistanceToActualBaseline(TextBaseline baseline) { + assert(!needsLayout); + if (_direction == FlexDirection.horizontal) + return defaultGetDistanceToHighestActualBaseline(baseline); + return defaultGetDistanceToFirstActualBaseline(baseline); + } + int _getFlex(RenderBox child) { assert(child.parentData is FlexBoxParentData); return child.parentData.flex != null ? child.parentData.flex : 0; @@ -267,6 +277,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin 0) { @@ -306,6 +317,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin