| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- import 'package:flutter/material.dart';
- import 'package:tuple/tuple.dart';
- import '../model/document/node/leaf.dart' as leaf;
- import '../model/document/attribute.dart';
- import '../model/document/node/line.dart';
- import '../model/document/node/node.dart';
- import '../rendering/text_line.dart';
- import '../widget/proxy.dart';
- import '../service/style.dart';
- import '../service/cursor.dart';
- import '../util/color.dart';
- /* --------------------------------- Widget --------------------------------- */
- class TextLine extends StatelessWidget {
- const TextLine({
- required this.line,
- required this.embedBuilder,
- required this.styles,
- this.textDirection,
- Key? key,
- }) : super(key: key);
- final Line line;
- final TextDirection? textDirection;
- final EmbedBuilderFuncion embedBuilder;
- final DefaultStyles styles;
- @override
- Widget build(BuildContext context) {
- assert(debugCheckHasMediaQuery(context));
- if (line.hasEmbed) {
- final embed = line.children.single as leaf.Embed;
- return EmbedProxy(embedBuilder(context, embed));
- }
- final textSpan = _buildTextSpan(context);
- final strutStyle = StrutStyle.fromTextStyle(textSpan.style!);
- final textAlign = _getTextAlign();
- final child = RichText(
- text: textSpan,
- textAlign: textAlign,
- textDirection: textDirection,
- strutStyle: strutStyle,
- textScaleFactor: MediaQuery.textScaleFactorOf(context),
- );
- return RichTextProxy(
- child,
- textSpan.style!,
- textAlign,
- textDirection!,
- 1,
- Localizations.localeOf(context),
- strutStyle,
- TextWidthBasis.parent,
- null,
- );
- }
- // TextStyle Decode
- TextAlign _getTextAlign() {
- final alignment = line.style.attributes[Attribute.align.key];
- if (Attribute.leftAlignment == alignment) {
- return TextAlign.left;
- } else if (Attribute.centerAlignment == alignment) {
- return TextAlign.center;
- } else if (Attribute.rightAlignment == alignment) {
- return TextAlign.right;
- } else if (Attribute.justifyAlignment == alignment) {
- return TextAlign.justify;
- }
- return TextAlign.start;
- }
- // Span Util
- TextSpan _buildTextSpan(BuildContext context) {
- final defaultStyles = styles;
- final children = line.children
- .map((child) => _getTextSpanFromNode(defaultStyles, child))
- .toList(growable: false);
- var textStyle = const TextStyle();
- // Placeholder
- if (line.style.containsKey(Attribute.placeholder.key)) {
- textStyle = defaultStyles.placeHolder!.style;
- return TextSpan(children: children, style: textStyle);
- }
- // Header
- final header = line.style.attributes[Attribute.header.key];
- final headerStyles = <Attribute, TextStyle>{
- Attribute.h1: defaultStyles.h1!.style,
- Attribute.h2: defaultStyles.h2!.style,
- Attribute.h3: defaultStyles.h3!.style,
- Attribute.h4: defaultStyles.h4!.style,
- Attribute.h5: defaultStyles.h5!.style,
- Attribute.h6: defaultStyles.h6!.style,
- };
- textStyle =
- textStyle.merge(headerStyles[header] ?? defaultStyles.paragraph!.style);
- // Block
- final block = line.style.getBlockExceptHeader();
- TextStyle? blockStyle;
- if (Attribute.quoteBlock == block) {
- blockStyle = defaultStyles.quote!.style;
- } else if (Attribute.codeBlock == block) {
- blockStyle = defaultStyles.code!.style;
- } else if (block != null) {
- blockStyle = defaultStyles.lists!.style;
- }
- textStyle = textStyle.merge(blockStyle);
- return TextSpan(children: children, style: textStyle);
- }
- TextSpan _getTextSpanFromNode(DefaultStyles defaultStyles, Node node) {
- final textNode = node as leaf.Text;
- final style = textNode.style;
- // Inline Style
- final inlineStyles = <String, TextStyle?>{
- Attribute.bold.key: defaultStyles.bold,
- Attribute.italic.key: defaultStyles.italic,
- Attribute.link.key: defaultStyles.link,
- Attribute.underline.key: defaultStyles.underline,
- Attribute.strikeThrough.key: defaultStyles.strikeThrough,
- };
- var result = const TextStyle();
- inlineStyles.forEach((_key, _style) {
- if (style.values.any((val) => val.key == _key)) {
- result = _merge(result, _style!);
- }
- });
- // Font
- final font = textNode.style.attributes[Attribute.font.key];
- if (font != null && font.value != null) {
- result = result.merge(TextStyle(fontFamily: font.value));
- }
- // Size
- final size = textNode.style.attributes[Attribute.size.key];
- if (size != null && size.value != null) {
- switch (size.value) {
- case 'small':
- result = result.merge(defaultStyles.sizeSmall);
- break;
- case 'large':
- result = result.merge(defaultStyles.sizeLarge);
- break;
- case 'huge':
- result = result.merge(defaultStyles.sizeHuge);
- break;
- default:
- final fontSize = double.tryParse(size.value);
- if (fontSize != null) {
- result = result.merge(TextStyle(fontSize: fontSize));
- } else {
- throw 'Invalid size ${size.value}';
- }
- }
- }
- // Color
- final color = textNode.style.attributes[Attribute.color.key];
- if (color != null && color.value != null) {
- var textColor = defaultStyles.color;
- if (color.value is String) {
- textColor = stringToColor(color.value);
- }
- if (textColor != null) {
- result = result.merge(TextStyle(color: textColor));
- }
- }
- // Background
- final background = textNode.style.attributes[Attribute.background.key];
- if (background != null && background.value != null) {
- final backgroundColor = stringToColor(background.value);
- result = result.merge(TextStyle(backgroundColor: backgroundColor));
- }
- return TextSpan(text: textNode.value, style: result);
- }
- TextStyle _merge(TextStyle style, TextStyle otherStyle) {
- final decorations = <TextDecoration?>[];
- if (style.decoration != null) {
- decorations.add(style.decoration);
- }
- if (otherStyle.decoration != null) {
- decorations.add(otherStyle.decoration);
- }
- return style.merge(otherStyle).apply(
- decoration: TextDecoration.combine(
- List.castFrom<dynamic, TextDecoration>(decorations),
- ),
- );
- }
- }
- /* -------------------------- Render Object Widget -------------------------- */
- class EditableTextLine extends RenderObjectWidget {
- const EditableTextLine(
- this.line,
- this.leading,
- this.body,
- this.indentWidth,
- this.verticalSpacing,
- this.textDirection,
- this.textSelection,
- this.color,
- this.enableInteractiveSelection,
- this.hasFocus,
- this.devicePixelRatio,
- this.cursorController,
- );
- final Line line;
- final Widget? leading;
- final Widget body;
- final double indentWidth;
- final Tuple2 verticalSpacing;
- final TextDirection textDirection;
- final TextSelection textSelection;
- final Color color;
- final bool enableInteractiveSelection;
- final bool hasFocus;
- final double devicePixelRatio;
- final CursorController cursorController;
- @override
- RenderObjectElement createElement() => TextLineElement(this);
- @override
- RenderEditableTextLine createRenderObject(BuildContext context) {
- return RenderEditableTextLine(
- line,
- textDirection,
- textSelection,
- enableInteractiveSelection,
- hasFocus,
- devicePixelRatio,
- _computePadding(),
- color,
- cursorController,
- );
- }
- @override
- void updateRenderObject(
- BuildContext context, covariant RenderEditableTextLine renderObject) {
- renderObject
- ..line = line
- ..padding = _computePadding()
- ..textDirection = textDirection
- ..textSelection = textSelection
- ..color = color
- ..enableInteractiveSelection = enableInteractiveSelection
- ..hasFocus = hasFocus
- ..devicePixelRatio = devicePixelRatio
- ..cursorController = cursorController;
- }
- EdgeInsetsGeometry _computePadding() {
- return EdgeInsetsDirectional.only(
- start: indentWidth,
- top: verticalSpacing.item1,
- bottom: verticalSpacing.item2,
- );
- }
- }
|