text_painter.dart 5.88 KB
Newer Older
1 2 3 4 5 6
// 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;

7
import 'package:sky/src/painting/text_style.dart';
8

9
export 'package:sky/src/painting/text_style.dart';
10

11
/// An immutable span of text
12
abstract class TextSpan {
13
  // This class must be immutable, because we won't notice when it changes
14 15 16 17 18 19 20
  sky.Node _toDOM(sky.Document owner);
  String toString([String prefix = '']);

  void _applyStyleToContainer(sky.Element container) {
  }
}

21
/// An immutable span of unstyled text
22 23 24 25 26
class PlainTextSpan extends TextSpan {
  PlainTextSpan(this.text) {
    assert(text != null);
  }

27
  /// The text contained in the span
28 29 30 31 32 33 34 35 36 37 38 39
  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}"';
}

40
/// An immutable text span that applies a style to a list of children
41 42 43 44 45 46
class StyledTextSpan extends TextSpan {
  StyledTextSpan(this.style, this.children) {
    assert(style != null);
    assert(children != null);
  }

47
  /// The style to apply to the children
48
  final TextStyle style;
49 50

  /// The children to which the style is applied
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  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');
  }
}

100
/// An object that paints a [TextSpan] into a canvas
101 102
class TextPainter {
  TextPainter(TextSpan text) {
103 104 105 106 107 108 109
    _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();
110
  bool _needsLayout = true;
111 112

  TextSpan _text;
113
  /// The (potentially styled) text to paint
114 115
  TextSpan get text => _text;
  void set text(TextSpan value) {
116 117
    if (_text == value)
      return;
118 119 120 121
    _text = value;
    _layoutRoot.rootElement.setChild(_text._toDOM(_document));
    _layoutRoot.rootElement.removeAttribute('style');
    _text._applyStyleToContainer(_layoutRoot.rootElement);
122
    _needsLayout = true;
123 124
  }

125
  /// The minimum width at which to layout the text
126 127
  double get minWidth => _layoutRoot.minWidth;
  void set minWidth(value) {
128 129
    if (_layoutRoot.minWidth == value)
      return;
130
    _layoutRoot.minWidth = value;
131
    _needsLayout = true;
132 133
  }

134
  /// The maximum width at which to layout the text
135 136
  double get maxWidth => _layoutRoot.maxWidth;
  void set maxWidth(value) {
137 138
    if (_layoutRoot.maxWidth == value)
      return;
139
    _layoutRoot.maxWidth = value;
140
    _needsLayout = true;
141 142
  }

143
  /// The minimum height at which to layout the text
144 145
  double get minHeight => _layoutRoot.minHeight;
  void set minHeight(value) {
146 147
    if (_layoutRoot.minHeight == value)
      return;
148
    _layoutRoot.minHeight = value;
149
    _needsLayout = true;
150 151
  }

152
  /// The maximum height at which to layout the text
153 154
  double get maxHeight => _layoutRoot.maxHeight;
  void set maxHeight(value) {
155 156
    if (_layoutRoot.maxHeight == value)
      return;
157 158 159
    _layoutRoot.maxHeight = value;
  }

160
  /// The width at which decreasing the width of the text would prevent it from painting itself completely within its bounds
161 162 163 164 165
  double get minContentWidth {
    assert(!_needsLayout);
    return _layoutRoot.rootElement.minContentWidth;
  }

166
  /// The width at which increasing the width of the text no longer decreases the height
167 168 169 170 171
  double get maxContentWidth {
    assert(!_needsLayout);
    return _layoutRoot.rootElement.maxContentWidth;
  }

172
  /// The height required to paint the text completely within its bounds
173 174 175 176 177
  double get height {
    assert(!_needsLayout);
    return _layoutRoot.rootElement.height;
  }

178
  /// The distance from the top of the text to the first baseline of the given type
179
  double computeDistanceToActualBaseline(TextBaseline baseline) {
180
    assert(!_needsLayout);
181 182 183 184 185 186 187
    sky.Element root = _layoutRoot.rootElement;
    switch (baseline) {
      case TextBaseline.alphabetic: return root.alphabeticBaseline;
      case TextBaseline.ideographic: return root.ideographicBaseline;
    }
  }

188
  /// Compute the visual position of the glyphs for painting the text
189 190 191 192 193 194
  void layout() {
    if (!_needsLayout)
      return;
    _layoutRoot.layout();
    _needsLayout = false;
  }
195

196
  /// Paint the text onto the given canvas at the given offset
197
  void paint(sky.Canvas canvas, sky.Offset offset) {
198
    assert(!_needsLayout && "Please call layout() before paint() to position the text before painting it." is String);
199 200 201 202 203 204 205
    // 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);
  }
}