LCOV - code coverage report
Current view: top level - text - mongol_render_paragraph.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 122 152 80.3 %
Date: 2021-07-30 09:13:58 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2014 The Flutter Authors.
       2             : // Copyright 2021 Suragch.
       3             : // All rights reserved.
       4             : // Use of this source code is governed by a BSD-style license that can be
       5             : // found in the LICENSE file.
       6             : 
       7             : import 'dart:ui' as ui show Gradient, Shader;
       8             : 
       9             : import 'package:flutter/foundation.dart';
      10             : import 'package:flutter/rendering.dart';
      11             : import 'package:flutter/widgets.dart';
      12             : import 'package:mongol/src/base/mongol_text_align.dart';
      13             : 
      14             : import '../base/mongol_text_painter.dart';
      15             : 
      16             : // TODO: This is a horizontal elipsis. Should we use a Mongolian elipsis U+1801?
      17             : const String _kEllipsis = '\u2026';
      18             : 
      19             : /// A render object that displays a paragraph of vertical Mongolian text.
      20             : class MongolRenderParagraph extends RenderBox
      21             :     with
      22             :         ContainerRenderObjectMixin<RenderBox, TextParentData>,
      23             :         RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>,
      24             :         RelayoutWhenSystemFontsChangeMixin {
      25             :   /// Creates a vertical paragraph render object.
      26             :   ///
      27             :   /// The [maxLines] property may be null (and indeed defaults to null), but if
      28             :   /// it is not null, it must be greater than zero.
      29           7 :   MongolRenderParagraph(
      30             :     TextSpan text, {
      31             :     MongolTextAlign textAlign = MongolTextAlign.top,
      32             :     bool softWrap = true,
      33             :     TextOverflow overflow = TextOverflow.clip,
      34             :     double textScaleFactor = 1.0,
      35             :     int? maxLines,
      36           1 :   })  : assert(maxLines == null || maxLines > 0),
      37             :         _softWrap = softWrap,
      38             :         _overflow = overflow,
      39           7 :         _textPainter = MongolTextPainter(
      40             :           text: text,
      41             :           textAlign: textAlign,
      42             :           textScaleFactor: textScaleFactor,
      43             :           maxLines: maxLines,
      44           7 :           ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
      45             :         );
      46             : 
      47           0 :   @override
      48             :   void setupParentData(RenderBox child) {
      49           0 :     if (child.parentData is! TextParentData) {
      50           0 :       child.parentData = TextParentData();
      51             :     }
      52             :   }
      53             : 
      54             :   final MongolTextPainter _textPainter;
      55             : 
      56             :   /// The text to display
      57           0 :   TextSpan get text => _textPainter.text!;
      58           5 :   set text(TextSpan value) {
      59          15 :     switch (_textPainter.text!.compareTo(value)) {
      60           5 :       case RenderComparison.identical:
      61           4 :       case RenderComparison.metadata:
      62             :         return;
      63           4 :       case RenderComparison.paint:
      64           4 :         _textPainter.text = value;
      65           2 :         markNeedsPaint();
      66             :         break;
      67           3 :       case RenderComparison.layout:
      68           6 :         _textPainter.text = value;
      69           3 :         markNeedsLayout();
      70             :         break;
      71             :     }
      72             :   }
      73             : 
      74             :   /// How the text should be aligned vertically.
      75           0 :   MongolTextAlign get textAlign => _textPainter.textAlign;
      76           5 :   set textAlign(MongolTextAlign value) {
      77          15 :     if (_textPainter.textAlign == value) {
      78             :       return;
      79             :     }
      80           2 :     _textPainter.textAlign = value;
      81           1 :     markNeedsPaint();
      82             :   }
      83             : 
      84             :   /// Whether the text should break at soft line breaks.
      85             :   ///
      86             :   /// If false, the glyphs in the text will be positioned as if there was
      87             :   /// unlimited vertical space.
      88             :   ///
      89             :   /// If [softWrap] is false, [overflow] and [textAlign] may have unexpected
      90             :   /// effects.
      91          14 :   bool get softWrap => _softWrap;
      92             :   bool _softWrap;
      93           5 :   set softWrap(bool value) {
      94          10 :     if (_softWrap == value) {
      95             :       return;
      96             :     }
      97           1 :     _softWrap = value;
      98           1 :     markNeedsLayout();
      99             :   }
     100             : 
     101             :   /// How visual overflow should be handled.
     102           4 :   TextOverflow get overflow => _overflow;
     103             :   TextOverflow _overflow;
     104           5 :   set overflow(TextOverflow value) {
     105          10 :     if (_overflow == value) {
     106             :       return;
     107             :     }
     108           1 :     _overflow = value;
     109           3 :     _textPainter.ellipsis = value == TextOverflow.ellipsis ? _kEllipsis : null;
     110           1 :     markNeedsLayout();
     111             :   }
     112             : 
     113             :   /// The number of font pixels for each logical pixel.
     114             :   ///
     115             :   /// For example, if the text scale factor is 1.5, text will be 50% larger than
     116             :   /// the specified font size.
     117           3 :   double get textScaleFactor => _textPainter.textScaleFactor;
     118           5 :   set textScaleFactor(double value) {
     119          15 :     if (_textPainter.textScaleFactor == value) return;
     120           2 :     _textPainter.textScaleFactor = value;
     121           1 :     markNeedsLayout();
     122             :   }
     123             : 
     124             :   /// An optional maximum number of lines for the text to span, wrapping if
     125             :   /// necessary. If the text exceeds the given number of lines, it will be
     126             :   /// truncated according to [overflow] and [softWrap].
     127           0 :   int? get maxLines => _textPainter.maxLines;
     128             : 
     129             :   /// The value may be null. If it is not null, then it must be greater than
     130             :   /// zero.
     131           5 :   set maxLines(int? value) {
     132           2 :     assert(value == null || value > 0);
     133          15 :     if (_textPainter.maxLines == value) {
     134             :       return;
     135             :     }
     136           2 :     _textPainter.maxLines = value;
     137           1 :     _overflowShader = null;
     138           1 :     markNeedsLayout();
     139             :   }
     140             : 
     141             :   bool _needsClipping = false;
     142             :   ui.Shader? _overflowShader;
     143             : 
     144             :   /// Whether this paragraph currently has a [dart:ui.Shader] for its overflow
     145             :   /// effect.
     146             :   ///
     147             :   /// Used to test this object. Not for use in production.
     148           0 :   @visibleForTesting
     149           0 :   bool get debugHasOverflowShader => _overflowShader != null;
     150             : 
     151           7 :   void _layoutText({
     152             :     double minHeight = 0.0,
     153             :     double maxHeight = double.infinity,
     154             :   }) {
     155          11 :     final heightMatters = softWrap || overflow == TextOverflow.ellipsis;
     156          14 :     _textPainter.layout(
     157             :       minHeight: minHeight,
     158             :       maxHeight: heightMatters ? maxHeight : double.infinity,
     159             :     );
     160             :   }
     161             : 
     162           7 :   void _layoutTextWithConstraints(BoxConstraints constraints) {
     163           7 :     _layoutText(
     164           7 :       minHeight: constraints.minHeight,
     165           7 :       maxHeight: constraints.maxHeight,
     166             :     );
     167             :   }
     168             : 
     169           4 :   @override
     170             :   double computeMinIntrinsicHeight(double width) {
     171           4 :     _layoutText();
     172           8 :     return _textPainter.minIntrinsicHeight;
     173             :   }
     174             : 
     175           4 :   @override
     176             :   double computeMaxIntrinsicHeight(double width) {
     177           4 :     _layoutText();
     178           8 :     return _textPainter.maxIntrinsicHeight;
     179             :   }
     180             : 
     181           4 :   double _computeIntrinsicWidth(double height) {
     182           4 :     _layoutText(minHeight: height, maxHeight: height);
     183           8 :     return _textPainter.width;
     184             :   }
     185             : 
     186           4 :   @override
     187             :   double computeMinIntrinsicWidth(double height) {
     188           4 :     return _computeIntrinsicWidth(height);
     189             :   }
     190             : 
     191           4 :   @override
     192             :   double computeMaxIntrinsicWidth(double height) {
     193           4 :     return _computeIntrinsicWidth(height);
     194             :   }
     195             : 
     196           2 :   @override
     197             :   double computeDistanceToActualBaseline(TextBaseline baseline) {
     198           2 :     assert(!debugNeedsLayout);
     199           4 :     assert(constraints.debugAssertIsValid());
     200           4 :     _layoutTextWithConstraints(constraints);
     201           4 :     return _textPainter.computeDistanceToActualBaseline(baseline);
     202             :   }
     203             : 
     204           3 :   @override
     205             :   bool hitTestSelf(Offset position) => true;
     206             : 
     207           3 :   @override
     208             :   void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
     209           3 :     assert(debugHandleEvent(event, entry));
     210           3 :     if (event is! PointerDownEvent) return;
     211           6 :     _layoutTextWithConstraints(constraints);
     212           3 :     final offset = entry.localPosition;
     213           6 :     final position = _textPainter.getPositionForOffset(offset);
     214           9 :     final span = _textPainter.text!.getSpanForPosition(position);
     215             :     if (span == null) {
     216             :       return;
     217             :     }
     218           3 :     if (span is TextSpan) {
     219           3 :       span.recognizer?.addPointer(event);
     220             :     }
     221             :   }
     222             : 
     223           7 :   @override
     224             :   void performLayout() {
     225           7 :     final constraints = this.constraints;
     226           7 :     _layoutTextWithConstraints(constraints);
     227             :     // final textSize = _textPainter.size;
     228             :     // size = constraints.constrain(textSize);
     229             : 
     230             :     // We grab _textPainter.size and _textPainter.didExceedMaxLines here because
     231             :     // assigning to `size` will trigger us to validate our intrinsic sizes,
     232             :     // which will change _textPainter's layout because the intrinsic size
     233             :     // calculations are destructive. Other _textPainter state will also be
     234             :     // affected. See also MongolRenderEditable which has a similar issue.
     235          14 :     final textSize = _textPainter.size;
     236          14 :     final textDidExceedMaxLines = _textPainter.didExceedMaxLines;
     237          14 :     size = constraints.constrain(textSize);
     238             : 
     239             :     final didOverflowWidth =
     240          28 :         size.width < textSize.width || textDidExceedMaxLines;
     241          28 :     final didOverflowHeight = size.height < textSize.height;
     242             :     final hasVisualOverflow = didOverflowHeight || didOverflowWidth;
     243             :     if (hasVisualOverflow) {
     244           1 :       switch (_overflow) {
     245           1 :         case TextOverflow.visible:
     246           0 :           _needsClipping = false;
     247           0 :           _overflowShader = null;
     248             :           break;
     249           1 :         case TextOverflow.clip:
     250           1 :         case TextOverflow.ellipsis:
     251           1 :           _needsClipping = true;
     252           1 :           _overflowShader = null;
     253             :           break;
     254           1 :         case TextOverflow.fade:
     255           1 :           _needsClipping = true;
     256           1 :           final fadeSizePainter = MongolTextPainter(
     257           4 :             text: TextSpan(style: _textPainter.text!.style, text: '\u2026'),
     258           1 :             textScaleFactor: textScaleFactor,
     259           1 :           )..layout();
     260             :           if (didOverflowWidth) {
     261             :             double fadeEnd, fadeStart;
     262           2 :             fadeEnd = size.height;
     263           2 :             fadeStart = fadeEnd - fadeSizePainter.height;
     264           2 :             _overflowShader = ui.Gradient.linear(
     265           1 :               Offset(0.0, fadeStart),
     266           1 :               Offset(0.0, fadeEnd),
     267           1 :               <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
     268             :             );
     269             :           } else {
     270           0 :             final fadeEnd = size.width;
     271           0 :             final fadeStart = fadeEnd - fadeSizePainter.width / 2.0;
     272           0 :             _overflowShader = ui.Gradient.linear(
     273           0 :               Offset(fadeStart, 0.0),
     274           0 :               Offset(fadeEnd, 0.0),
     275           0 :               <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
     276             :             );
     277             :           }
     278             :           break;
     279             :       }
     280             :     } else {
     281           7 :       _needsClipping = false;
     282           7 :       _overflowShader = null;
     283             :     }
     284             :   }
     285             : 
     286           7 :   @override
     287             :   void paint(PaintingContext context, Offset offset) {
     288          14 :     _layoutTextWithConstraints(constraints);
     289           7 :     assert(() {
     290             :       if (debugRepaintTextRainbowEnabled) {
     291           0 :         final paint = Paint()..color = debugCurrentRepaintColor.toColor();
     292           0 :         context.canvas.drawRect(offset & size, paint);
     293             :       }
     294             :       return true;
     295           7 :     }());
     296             : 
     297           7 :     if (_needsClipping) {
     298           2 :       final bounds = offset & size;
     299           1 :       if (_overflowShader != null) {
     300             :         // This layer limits what the shader below blends with to be just the
     301             :         // text (as opposed to the text and its background).
     302           3 :         context.canvas.saveLayer(bounds, Paint());
     303             :       } else {
     304           2 :         context.canvas.save();
     305             :       }
     306           2 :       context.canvas.clipRect(bounds);
     307             :     }
     308             : 
     309          21 :     _textPainter.paint(context.canvas, offset);
     310             : 
     311           7 :     if (_needsClipping) {
     312           1 :       if (_overflowShader != null) {
     313           4 :         context.canvas.translate(offset.dx, offset.dy);
     314           1 :         final paint = Paint()
     315           1 :           ..blendMode = BlendMode.modulate
     316           2 :           ..shader = _overflowShader;
     317           4 :         context.canvas.drawRect(Offset.zero & size, paint);
     318             :       }
     319           2 :       context.canvas.restore();
     320             :     }
     321             :   }
     322             : 
     323           0 :   @override
     324             :   List<DiagnosticsNode> debugDescribeChildren() {
     325           0 :     return <DiagnosticsNode>[
     326           0 :       text.toDiagnosticsNode(
     327             :           name: 'text', style: DiagnosticsTreeStyle.transition)
     328             :     ];
     329             :   }
     330             : 
     331           0 :   @override
     332             :   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     333           0 :     super.debugFillProperties(properties);
     334           0 :     properties.add(EnumProperty<MongolTextAlign>('textAlign', textAlign));
     335           0 :     properties.add(FlagProperty(
     336             :       'softWrap',
     337           0 :       value: softWrap,
     338             :       ifTrue: 'wrapping at box height',
     339             :       ifFalse: 'no wrapping except at line break characters',
     340             :       showName: true,
     341             :     ));
     342           0 :     properties.add(EnumProperty<TextOverflow>('overflow', overflow));
     343           0 :     properties.add(
     344           0 :         DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0));
     345           0 :     properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
     346             :   }
     347             : }

Generated by: LCOV version 1.15