// 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/box.dart'; import 'package:sky/rendering/object.dart'; abstract class RenderInline { sky.Node _toDOM(sky.Document owner); String toString([String prefix = '']); void _applyStyleToContainer(sky.Element container) { } } class RenderText extends RenderInline { RenderText(this.text) { assert(text != null); } final String text; sky.Node _toDOM(sky.Document owner) { return owner.createText(text); } bool operator ==(other) => other is RenderText && text == other.text; int get hashCode => text.hashCode; String toString([String prefix = '']) => '${prefix}InlineText: "${text}"'; } class RenderStyled extends RenderInline { RenderStyled(this.style, this.children) { assert(style != null); assert(children != null); } final TextStyle style; final List<RenderInline> children; sky.Node _toDOM(sky.Document owner) { sky.Element parent = owner.createElement('t'); style.applyToCSSStyle(parent.style); for (RenderInline child in children) { parent.appendChild(child._toDOM(owner)); } return parent; } void _applyStyleToContainer(sky.Element container) { style.applyToContainerCSSStyle(container.style); } bool operator ==(other) { if (identical(this, other)) return true; if (other is! RenderStyled || style != other.style || children.length != other.children.length) return false; for (int i = 0; i < children.length; ++i) { if (children[i] != other.children[i]) return false; } return true; } int get hashCode { int value = 373; value = 37 * value + style.hashCode; for (RenderInline child in children) value = 37 * value + child.hashCode; return value; } String toString([String prefix = '']) { List<String> result = []; result.add('${prefix}InlineStyle:'); var indent = '${prefix} '; result.add('${style.toString(indent)}'); for (RenderInline child in children) { result.add(child.toString(indent)); } return result.join('\n'); } } // Unfortunately, using full precision floating point here causes bad layouts // because floating point math isn't associative. If we add and subtract // padding, for example, we'll get different values when we estimate sizes and // when we actually compute layout because the operations will end up associated // differently. To work around this problem for now, we round fractional pixel // values up to the nearest whole pixel value. The right long-term fix is to do // layout using fixed precision arithmetic. double _applyFloatingPointHack(double layoutValue) { return layoutValue.ceilToDouble(); } class RenderParagraph extends RenderBox { RenderParagraph(RenderInline inline) { _layoutRoot.rootElement = _document.createElement('p'); this.inline = inline; } final sky.Document _document = new sky.Document(); final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot(); BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout RenderInline _inline; RenderInline get inline => _inline; void set inline (RenderInline value) { if (_inline == value) return; _inline = value; _layoutRoot.rootElement.setChild(_inline._toDOM(_document)); _layoutRoot.rootElement.removeAttribute('style'); _inline._applyStyleToContainer(_layoutRoot.rootElement); _constraintsForCurrentLayout = null; markNeedsLayout(); } void _layout(BoxConstraints constraints) { assert(constraints != null); if (_constraintsForCurrentLayout == constraints) return; // already cached this layout _layoutRoot.maxWidth = constraints.maxWidth; _layoutRoot.minWidth = constraints.minWidth; _layoutRoot.minHeight = constraints.minHeight; _layoutRoot.maxHeight = constraints.maxHeight; _layoutRoot.layout(); _constraintsForCurrentLayout = constraints; } double getMinIntrinsicWidth(BoxConstraints constraints) { _layout(constraints); return constraints.constrainWidth( _applyFloatingPointHack(_layoutRoot.rootElement.minContentWidth)); } double getMaxIntrinsicWidth(BoxConstraints constraints) { _layout(constraints); return constraints.constrainWidth( _applyFloatingPointHack(_layoutRoot.rootElement.maxContentWidth)); } double _getIntrinsicHeight(BoxConstraints constraints) { _layout(constraints); return constraints.constrainHeight( _applyFloatingPointHack(_layoutRoot.rootElement.height)); } double getMinIntrinsicHeight(BoxConstraints constraints) { return _getIntrinsicHeight(constraints); } double getMaxIntrinsicHeight(BoxConstraints constraints) { return _getIntrinsicHeight(constraints); } double computeDistanceToActualBaseline(TextBaseline baseline) { assert(!needsLayout); _layout(constraints); sky.Element root = _layoutRoot.rootElement; switch (baseline) { case TextBaseline.alphabetic: return root.alphabeticBaseline; case TextBaseline.ideographic: return root.ideographicBaseline; } } void performLayout() { _layout(constraints); sky.Element root = _layoutRoot.rootElement; // rootElement.width always expands to fill, use maxContentWidth instead. size = constraints.constrain(new Size(_applyFloatingPointHack(root.maxContentWidth), _applyFloatingPointHack(root.height))); } void paint(PaintingCanvas canvas, Offset offset) { // Ideally we could compute the min/max intrinsic width/height with a // non-destructive operation. However, currently, computing these values // will destroy state inside the layout root. If that happens, we need to // get back the correct state by calling _layout again. // // TODO(abarth): Make computing the min/max intrinsic width/height // a non-destructive operation. // TODO(ianh): Make LayoutRoot support a paint offset so we don't // need to translate for each span of text. _layout(constraints); canvas.translate(offset.dx, offset.dy); _layoutRoot.paint(canvas); canvas.translate(-offset.dx, -offset.dy); } // we should probably expose a way to do precise (inter-glpyh) hit testing String debugDescribeSettings(String prefix) { String result = '${super.debugDescribeSettings(prefix)}'; result += '${prefix}inline:\n${inline.toString("$prefix ")}\n'; return result; } }