import 'dart:math' as math; import 'package:quill_delta/quill_delta.dart'; import '../style.dart'; import 'embed.dart'; import 'line.dart'; import 'node.dart'; /* A leaf node in document tree */ abstract class Leaf extends Node { Object _value; Object get value => _value; Leaf.val(Object val) : assert(val != null), _value = val; factory Leaf([Object data]) { assert(data != null); if (data is Embeddable) { return Embed(data); } String text = data as String; assert(text.isNotEmpty); return Text(text); } @override void applyStyle(Style value) { assert(value != null && (value.isInline || value.isEmpty), 'Unable to apply Style to leaf: $value'); super.applyStyle(value); } @override Line get parent => super.parent as Line; @override int get length { if (_value is String) { return (_value as String).length; } // return 1 for embedded object return 1; } @override Delta toDelta() { var data = _value is Embeddable ? (_value as Embeddable).toJson() : _value; return Delta()..insert(data, style.toJson()); } @override insert(int index, Object data, Style style) { assert(data != null && index >= 0 && index <= length); Leaf node = Leaf(data); if (index < length) { splitAt(index).insertBefore(node); } else { insertAfter(node); } node.format(style); } @override retain(int index, int len, Style style) { if (style == null) { return; } int local = math.min(this.length - index, len); int remain = len - local; Leaf node = _isolate(index, local); if (remain > 0) { assert(node.next != null); node.next.retain(0, remain, style); } node.format(style); } @override delete(int index, int len) { assert(index < this.length); int local = math.min(this.length - index, len); Leaf target = _isolate(index, local); Leaf prev = target.previous; Leaf next = target.next; target.unlink(); int remain = len - local; if (remain > 0) { assert(next != null); next.delete(0, remain); } if (prev != null) { prev.adjust(); } } @override adjust() { if (this is Embed) { return; } Text node = this as Text; // merging it with previous node if style is the same Node prev = node.previous; if (!node.isFirst && prev is Text && prev.style == node.style) { prev._value = prev.value + node.value; node.unlink(); node = prev; } // merging it with next node if style is the same Node next = node.next; if (!node.isLast && next is Text && next.style == node.style) { node._value = node.value + next.value; next.unlink(); } } Leaf cutAt(int index) { assert(index >= 0 && index <= length); Leaf cut = splitAt(index); cut?.unlink(); return cut; } Leaf splitAt(int index) { assert(index >= 0 && index <= length); if (index == 0) { return this; } if (index == length) { return isLast ? null : next as Leaf; } assert(this is Text); String text = _value as String; _value = text.substring(0, index); Leaf split = Leaf(text.substring(index)); split.applyStyle(style); insertAfter(split); return split; } format(Style style) { if (style != null && style.isNotEmpty) { applyStyle(style); } adjust(); } Leaf _isolate(int index, int length) { assert( index >= 0 && index < this.length && (index + length <= this.length)); Leaf target = splitAt(index); target.splitAt(length); return target; } } class Text extends Leaf { Text([String text = '']) : assert(!text.contains('\n')), super.val(text); @override String get value => _value as String; @override String toPlainText() { return value; } @override Node newInstance() { return Text(); } } /// An embedded node such as image or video class Embed extends Leaf { Embed(Embeddable data) : super.val(data); @override Embeddable get value => super.value as Embeddable; @override String toPlainText() { return '\uFFFC'; } @override Node newInstance() { throw UnimplementedError(); } }