Commit 779f5743 authored by Collin Jackson's avatar Collin Jackson

Refactor of text rendering into painting layer

parent d291fcae
// 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';
// This must be immutable, because we won't notice when it changes
abstract class TextSpan {
sky.Node _toDOM(sky.Document owner);
String toString([String prefix = '']);
void _applyStyleToContainer(sky.Element container) {
}
}
class PlainTextSpan extends TextSpan {
PlainTextSpan(this.text) {
assert(text != null);
}
final String text;
sky.Node _toDOM(sky.Document owner) {
return owner.createText(text);
}
bool operator ==(other) => other is PlainTextSpan && text == other.text;
int get hashCode => text.hashCode;
String toString([String prefix = '']) => '${prefix}${runtimeType}: "${text}"';
}
class StyledTextSpan extends TextSpan {
StyledTextSpan(this.style, this.children) {
assert(style != null);
assert(children != null);
}
final TextStyle style;
final List<TextSpan> children;
sky.Node _toDOM(sky.Document owner) {
sky.Element parent = owner.createElement('t');
style.applyToCSSStyle(parent.style);
for (TextSpan 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! StyledTextSpan
|| 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 (TextSpan child in children)
value = 37 * value + child.hashCode;
return value;
}
String toString([String prefix = '']) {
List<String> result = [];
result.add('${prefix}${runtimeType}:');
var indent = '${prefix} ';
result.add('${style.toString(indent)}');
for (TextSpan child in children) {
result.add(child.toString(indent));
}
return result.join('\n');
}
}
class ParagraphPainter {
ParagraphPainter(TextSpan text) {
_layoutRoot.rootElement = _document.createElement('p');
assert(text != null);
this.text = text;
}
final sky.Document _document = new sky.Document();
final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot();
TextSpan _text;
TextSpan get text => _text;
void set text(TextSpan value) {
_text = value;
_layoutRoot.rootElement.setChild(_text._toDOM(_document));
_layoutRoot.rootElement.removeAttribute('style');
_text._applyStyleToContainer(_layoutRoot.rootElement);
}
double get minWidth => _layoutRoot.minWidth;
void set minWidth(value) {
_layoutRoot.minWidth = value;
}
double get maxWidth => _layoutRoot.maxWidth;
void set maxWidth(value) {
_layoutRoot.maxWidth = value;
}
double get minHeight => _layoutRoot.minHeight;
void set minHeight(value) {
_layoutRoot.minHeight = value;
}
double get maxHeight => _layoutRoot.maxHeight;
void set maxHeight(value) {
_layoutRoot.maxHeight = value;
}
double get minContentWidth => _layoutRoot.rootElement.minContentWidth;
double get maxContentWidth => _layoutRoot.rootElement.maxContentWidth;
double get height => _layoutRoot.rootElement.height;
double computeDistanceToActualBaseline(TextBaseline baseline) {
sky.Element root = _layoutRoot.rootElement;
switch (baseline) {
case TextBaseline.alphabetic: return root.alphabeticBaseline;
case TextBaseline.ideographic: return root.ideographicBaseline;
}
}
void layout() => _layoutRoot.layout();
void paint(sky.Canvas canvas, sky.Offset offset) {
// TODO(ianh): Make LayoutRoot support a paint offset so we don't
// need to translate for each span of text.
canvas.translate(offset.dx, offset.dy);
_layoutRoot.paint(canvas);
canvas.translate(-offset.dx, -offset.dy);
}
}
...@@ -2,92 +2,11 @@ ...@@ -2,92 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:sky' as sky; import 'package:sky/painting/paragraph_painter.dart';
import 'package:sky/painting/text_style.dart';
import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/object.dart'; import 'package:sky/rendering/object.dart';
abstract class RenderInline { export 'package:sky/painting/paragraph_painter.dart' show TextSpan, PlainTextSpan, StyledTextSpan;
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 // Unfortunately, using full precision floating point here causes bad layouts
// because floating point math isn't associative. If we add and subtract // because floating point math isn't associative. If we add and subtract
...@@ -102,25 +21,20 @@ double _applyFloatingPointHack(double layoutValue) { ...@@ -102,25 +21,20 @@ double _applyFloatingPointHack(double layoutValue) {
class RenderParagraph extends RenderBox { class RenderParagraph extends RenderBox {
RenderParagraph(RenderInline inline) { RenderParagraph(TextSpan text)
_layoutRoot.rootElement = _document.createElement('p'); : _paragraphPainter = new ParagraphPainter(text) {
this.inline = inline; assert(text != null);
} }
final sky.Document _document = new sky.Document(); ParagraphPainter _paragraphPainter;
final sky.LayoutRoot _layoutRoot = new sky.LayoutRoot();
BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout
RenderInline _inline; TextSpan get text => _paragraphPainter.text;
RenderInline get inline => _inline; void set text(TextSpan value) {
void set inline (RenderInline value) { if (_paragraphPainter.text == value)
if (_inline == value)
return; return;
_inline = value; _paragraphPainter.text = value;
_layoutRoot.rootElement.setChild(_inline._toDOM(_document));
_layoutRoot.rootElement.removeAttribute('style');
_inline._applyStyleToContainer(_layoutRoot.rootElement);
_constraintsForCurrentLayout = null; _constraintsForCurrentLayout = null;
markNeedsLayout(); markNeedsLayout();
} }
...@@ -129,30 +43,30 @@ class RenderParagraph extends RenderBox { ...@@ -129,30 +43,30 @@ class RenderParagraph extends RenderBox {
assert(constraints != null); assert(constraints != null);
if (_constraintsForCurrentLayout == constraints) if (_constraintsForCurrentLayout == constraints)
return; // already cached this layout return; // already cached this layout
_layoutRoot.maxWidth = constraints.maxWidth; _paragraphPainter.maxWidth = constraints.maxWidth;
_layoutRoot.minWidth = constraints.minWidth; _paragraphPainter.minWidth = constraints.minWidth;
_layoutRoot.minHeight = constraints.minHeight; _paragraphPainter.minHeight = constraints.minHeight;
_layoutRoot.maxHeight = constraints.maxHeight; _paragraphPainter.maxHeight = constraints.maxHeight;
_layoutRoot.layout(); _paragraphPainter.layout();
_constraintsForCurrentLayout = constraints; _constraintsForCurrentLayout = constraints;
} }
double getMinIntrinsicWidth(BoxConstraints constraints) { double getMinIntrinsicWidth(BoxConstraints constraints) {
_layout(constraints); _layout(constraints);
return constraints.constrainWidth( return constraints.constrainWidth(
_applyFloatingPointHack(_layoutRoot.rootElement.minContentWidth)); _applyFloatingPointHack(_paragraphPainter.minContentWidth));
} }
double getMaxIntrinsicWidth(BoxConstraints constraints) { double getMaxIntrinsicWidth(BoxConstraints constraints) {
_layout(constraints); _layout(constraints);
return constraints.constrainWidth( return constraints.constrainWidth(
_applyFloatingPointHack(_layoutRoot.rootElement.maxContentWidth)); _applyFloatingPointHack(_paragraphPainter.maxContentWidth));
} }
double _getIntrinsicHeight(BoxConstraints constraints) { double _getIntrinsicHeight(BoxConstraints constraints) {
_layout(constraints); _layout(constraints);
return constraints.constrainHeight( return constraints.constrainHeight(
_applyFloatingPointHack(_layoutRoot.rootElement.height)); _applyFloatingPointHack(_paragraphPainter.height));
} }
double getMinIntrinsicHeight(BoxConstraints constraints) { double getMinIntrinsicHeight(BoxConstraints constraints) {
...@@ -166,42 +80,33 @@ class RenderParagraph extends RenderBox { ...@@ -166,42 +80,33 @@ class RenderParagraph extends RenderBox {
double computeDistanceToActualBaseline(TextBaseline baseline) { double computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!needsLayout); assert(!needsLayout);
_layout(constraints); _layout(constraints);
sky.Element root = _layoutRoot.rootElement; return _paragraphPainter.computeDistanceToActualBaseline(baseline);
switch (baseline) {
case TextBaseline.alphabetic: return root.alphabeticBaseline;
case TextBaseline.ideographic: return root.ideographicBaseline;
}
} }
void performLayout() { void performLayout() {
_layout(constraints); _layout(constraints);
sky.Element root = _layoutRoot.rootElement; // _paragraphPainter.width always expands to fill, use maxContentWidth instead.
// rootElement.width always expands to fill, use maxContentWidth instead. size = constraints.constrain(new Size(_applyFloatingPointHack(_paragraphPainter.maxContentWidth),
size = constraints.constrain(new Size(_applyFloatingPointHack(root.maxContentWidth), _applyFloatingPointHack(_paragraphPainter.height)));
_applyFloatingPointHack(root.height)));
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
// Ideally we could compute the min/max intrinsic width/height with a // Ideally we could compute the min/max intrinsic width/height with a
// non-destructive operation. However, currently, computing these values // non-destructive operation. However, currently, computing these values
// will destroy state inside the layout root. If that happens, we need to // will destroy state inside the painter. If that happens, we need to
// get back the correct state by calling _layout again. // get back the correct state by calling _layout again.
// //
// TODO(abarth): Make computing the min/max intrinsic width/height // TODO(abarth): Make computing the min/max intrinsic width/height
// a non-destructive operation. // 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); _layout(constraints);
context.canvas.translate(offset.dx, offset.dy); _paragraphPainter.paint(context.canvas, offset);
_layoutRoot.paint(context.canvas);
context.canvas.translate(-offset.dx, -offset.dy);
} }
// we should probably expose a way to do precise (inter-glpyh) hit testing // we should probably expose a way to do precise (inter-glpyh) hit testing
String debugDescribeSettings(String prefix) { String debugDescribeSettings(String prefix) {
String result = '${super.debugDescribeSettings(prefix)}'; String result = '${super.debugDescribeSettings(prefix)}';
result += '${prefix}inline:\n${inline.toString("$prefix ")}\n'; result += '${prefix}text:\n${text.toString("$prefix ")}\n';
return result; return result;
} }
} }
...@@ -10,6 +10,7 @@ import 'package:sky/base/image_resource.dart'; ...@@ -10,6 +10,7 @@ import 'package:sky/base/image_resource.dart';
import 'package:sky/mojo/asset_bundle.dart'; import 'package:sky/mojo/asset_bundle.dart';
import 'package:sky/mojo/net/image_cache.dart' as image_cache; import 'package:sky/mojo/net/image_cache.dart' as image_cache;
import 'package:sky/painting/text_style.dart'; import 'package:sky/painting/text_style.dart';
import 'package:sky/painting/paragraph_painter.dart';
import 'package:sky/rendering/block.dart'; import 'package:sky/rendering/block.dart';
import 'package:sky/rendering/box.dart'; import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/flex.dart'; import 'package:sky/rendering/flex.dart';
...@@ -446,16 +447,16 @@ class Flexible extends ParentDataNode { ...@@ -446,16 +447,16 @@ class Flexible extends ParentDataNode {
} }
class Paragraph extends LeafRenderObjectWrapper { class Paragraph extends LeafRenderObjectWrapper {
Paragraph({ Key key, this.inline }) : super(key: key); Paragraph({ Key key, this.text }) : super(key: key);
final RenderInline inline; final TextSpan text;
RenderParagraph createNode() => new RenderParagraph(inline); RenderParagraph createNode() => new RenderParagraph(text);
RenderParagraph get root => super.root; RenderParagraph get root => super.root;
void syncRenderObject(Widget old) { void syncRenderObject(Widget old) {
super.syncRenderObject(old); super.syncRenderObject(old);
root.inline = inline; root.text = text;
} }
} }
...@@ -464,25 +465,25 @@ class StyledText extends Component { ...@@ -464,25 +465,25 @@ class StyledText extends Component {
// Where "string" is text to display and text-style is an instance of // Where "string" is text to display and text-style is an instance of
// TextStyle. The text-style applies to all of the elements that follow. // TextStyle. The text-style applies to all of the elements that follow.
StyledText({ this.elements, Key key }) : super(key: key) { StyledText({ this.elements, Key key }) : super(key: key) {
assert(_toInline(elements) != null); assert(_toSpan(elements) != null);
} }
final dynamic elements; final dynamic elements;
RenderInline _toInline(dynamic element) { TextSpan _toSpan(dynamic element) {
if (element is String) if (element is String)
return new RenderText(element); return new PlainTextSpan(element);
if (element is Iterable) { if (element is Iterable) {
dynamic first = element.first; dynamic first = element.first;
if (first is! TextStyle) if (first is! TextStyle)
throw new ArgumentError("First element of Iterable is a ${first.runtimeType} not a TextStyle"); throw new ArgumentError("First element of Iterable is a ${first.runtimeType} not a TextStyle");
return new RenderStyled(first, element.skip(1).map(_toInline).toList()); return new StyledTextSpan(first, element.skip(1).map(_toSpan).toList());
} }
throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable"); throw new ArgumentError("Element is ${element.runtimeType} not a String or an Iterable");
} }
Widget build() { Widget build() {
return new Paragraph(inline: _toInline(elements)); return new Paragraph(text: _toSpan(elements));
} }
} }
...@@ -493,7 +494,7 @@ class Text extends Component { ...@@ -493,7 +494,7 @@ class Text extends Component {
final TextStyle style; final TextStyle style;
Widget build() { Widget build() {
RenderInline inline = new RenderText(data); TextSpan text = new PlainTextSpan(data);
TextStyle defaultStyle = DefaultTextStyle.of(this); TextStyle defaultStyle = DefaultTextStyle.of(this);
TextStyle combinedStyle; TextStyle combinedStyle;
if (defaultStyle != null) { if (defaultStyle != null) {
...@@ -505,8 +506,8 @@ class Text extends Component { ...@@ -505,8 +506,8 @@ class Text extends Component {
combinedStyle = style; combinedStyle = style;
} }
if (combinedStyle != null) if (combinedStyle != null)
inline = new RenderStyled(combinedStyle, [inline]); text = new StyledTextSpan(combinedStyle, [text]);
return new Paragraph(inline: inline); return new Paragraph(text: text);
} }
} }
......
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