From 93c3df73e18563e463e38b11961b123ef912f769 Mon Sep 17 00:00:00 2001 From: suragch Date: Sat, 13 Mar 2021 18:30:40 +0800 Subject: [PATCH] Implemented MongolTextAlign --- CHANGELOG.md | 5 ++ README.md | 3 +- example/lib/demos/text_demo.dart | 83 ++++++++++++++++++--- example/pubspec.lock | 2 +- lib/mongol.dart | 1 + lib/src/base/mongol_paragraph.dart | 65 ++++++++++++---- lib/src/base/mongol_text_align.dart | 27 +++++++ lib/src/base/mongol_text_painter.dart | 25 +------ lib/src/editing/mongol_editable_text.dart | 1 + lib/src/editing/mongol_input_decorator.dart | 2 +- lib/src/editing/mongol_render_editable.dart | 1 + lib/src/editing/mongol_text_field.dart | 1 + lib/src/text/mongol_render_paragraph.dart | 1 + lib/src/text/mongol_rich_text.dart | 2 +- lib/src/text/mongol_text.dart | 1 + pubspec.yaml | 2 +- test/mongol_editable_text_test.dart | 1 + test/mongol_paragraph_test.dart | 12 ++- test/mongol_render_editable_test.dart | 1 + test/mongol_text_field_test.dart | 1 + test/mongol_text_painter_test.dart | 1 + 21 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 lib/src/base/mongol_text_align.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index c7067f9..58038ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.1.0] - 2021.3.13 + +- Implemented MongolTextAlign (supports top, center, bottom, justify) +- Known issue: Spaces after words are included in the measurements so MongolTextAlign.bottom aligns the space to the bottom and not the text itself. + ## [1.0.0] - 2021.3.6 - Null safe diff --git a/README.md b/README.md index fcff42f..cea8e12 100644 --- a/README.md +++ b/README.md @@ -148,4 +148,5 @@ This are not part of `mongol` library yet, but you can see an example of how to - Improve keyboard - Various other text based widgets - Support `WidgetSpan`. -- Add missing tests (currently commented out) \ No newline at end of file +- Add missing tests (currently commented out) +- For MongolTextAlign.bottom don't count final space in line height \ No newline at end of file diff --git a/example/lib/demos/text_demo.dart b/example/lib/demos/text_demo.dart index 1777ccb..71c1529 100644 --- a/example/lib/demos/text_demo.dart +++ b/example/lib/demos/text_demo.dart @@ -1,22 +1,85 @@ import 'package:flutter/material.dart'; import 'package:mongol/mongol.dart'; -class TextDemo extends StatelessWidget { +class TextDemo extends StatefulWidget { + @override + _TextDemoState createState() => _TextDemoState(); +} + +class _TextDemoState extends State { + var _alignment = MongolTextAlign.top; + + void _updateAlignment(MongolTextAlign alignment) { + setState(() { + _alignment = alignment; + }); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), - body: Padding( - padding: const EdgeInsets.all(20.0), - child: Center( - child: Container( - color: Colors.blue[100], - child: MongolText( - text, - style: TextStyle(fontSize: 30), + body: Stack( + children: [ + Column( + children: [ + Button( + title: 'top', + onPressed: () => _updateAlignment(MongolTextAlign.top), + ), + Button( + title: 'center', + onPressed: () => _updateAlignment(MongolTextAlign.center), + ), + Button( + title: 'bottom', + onPressed: () => _updateAlignment(MongolTextAlign.bottom), + ), + Button( + title: 'justify', + onPressed: () => _updateAlignment(MongolTextAlign.justify), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Center( + child: Container( + color: Colors.blue[100], + child: MongolText( + text, + style: TextStyle(fontSize: 30), + textAlign: _alignment, + ), + ), ), ), - ), + ], + ), + ); + } +} + +class Button extends StatelessWidget { + const Button({ + Key? key, + required this.title, + required this.onPressed, + }) : super(key: key); + + final String title; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: MaterialButton( + minWidth: 100, + elevation: 8, + color: Colors.blue, + child: Text(title), + onPressed: onPressed, ), ); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 9ea0c34..9655603 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -80,7 +80,7 @@ packages: path: ".." relative: true source: path - version: "1.0.0" + version: "1.1.0" path: dependency: transitive description: diff --git a/lib/mongol.dart b/lib/mongol.dart index 2272d7e..cec741f 100644 --- a/lib/mongol.dart +++ b/lib/mongol.dart @@ -2,6 +2,7 @@ library mongol; export 'package:mongol/src/text/mongol_text.dart'; export 'package:mongol/src/text/mongol_rich_text.dart'; +export 'package:mongol/src/base/mongol_text_align.dart'; export 'package:mongol/src/dialog/mongol_alert_dialog.dart'; export 'package:mongol/src/editing/mongol_text_field.dart'; export 'package:mongol/src/editing/alignment.dart'; diff --git a/lib/src/base/mongol_paragraph.dart b/lib/src/base/mongol_paragraph.dart index 7b7cf49..5792196 100644 --- a/lib/src/base/mongol_paragraph.dart +++ b/lib/src/base/mongol_paragraph.dart @@ -11,6 +11,8 @@ import 'dart:ui' as ui; import 'package:flutter/painting.dart'; import 'package:characters/characters.dart'; +import 'mongol_text_align.dart'; + /// A paragraph of vertical Mongolian layout text. /// /// This class is a replacement for the Paragraph class. Since Paragraph hands @@ -33,12 +35,14 @@ class MongolParagraph { this._text, this._maxLines, this._ellipsis, + this._textAlign, ); final String _text; final List<_TextRun> _runs; final int? _maxLines; final _TextRun? _ellipsis; + final MongolTextAlign _textAlign; double? _width; double? _height; @@ -295,26 +299,55 @@ class MongolParagraph { final dy = -line.bounds.height; canvas.translate(0, dy); - // draw each run in the current line - canvas.save(); - final startIndex = line.textRunStart; - final endIndex = line.textRunEnd - 1; + // draw line final isLastLine = i == _lines.length - 1; - for (var j = startIndex; j <= endIndex; j++) { - if (shouldDrawEllipsis && isLastLine && j == endIndex) { - if (maxIntrinsicHeight + _ellipsis!.height < height) { - final run = _runs[j]; - run.draw(canvas, Offset(0, 0)); - canvas.translate(run.width, 0); - } - _ellipsis!.draw(canvas, Offset(0, 0)); - } else { + _drawEachRunInCurrentLine(canvas, line, shouldDrawEllipsis, isLastLine); + } + + canvas.restore(); + } + + void _drawEachRunInCurrentLine( + Canvas canvas, _LineInfo line, bool shouldDrawEllipsis, bool isLastLine) { + canvas.save(); + + var runSpacing = 0.0; + switch (_textAlign) { + case MongolTextAlign.top: + break; + case MongolTextAlign.center: + final offset = (_height! - line.bounds.width) / 2; + canvas.translate(offset, 0); + break; + case MongolTextAlign.bottom: + final offset = _height! - line.bounds.width; + canvas.translate(offset, 0); + break; + case MongolTextAlign.justify: + if (isLastLine) break; + final extraSpace = _height! - line.bounds.width; + final runsInLine = line.textRunEnd - line.textRunStart; + if (runsInLine <= 1) break; + runSpacing = extraSpace / (runsInLine - 1); + break; + } + + final startIndex = line.textRunStart; + final endIndex = line.textRunEnd - 1; + for (var j = startIndex; j <= endIndex; j++) { + if (shouldDrawEllipsis && isLastLine && j == endIndex) { + if (maxIntrinsicHeight + _ellipsis!.height < height) { final run = _runs[j]; run.draw(canvas, Offset(0, 0)); canvas.translate(run.width, 0); } + _ellipsis!.draw(canvas, Offset(0, 0)); + } else { + final run = _runs[j]; + run.draw(canvas, Offset(0, 0)); + canvas.translate(run.width, 0); } - canvas.restore(); + canvas.translate(runSpacing, 0); } canvas.restore(); @@ -602,15 +635,18 @@ class MongolParagraphConstraints { class MongolParagraphBuilder { MongolParagraphBuilder( ui.ParagraphStyle style, { + MongolTextAlign textAlign = MongolTextAlign.top, double textScaleFactor = 1.0, int? maxLines, String? ellipsis, }) : _paragraphStyle = style, + _textAlign = textAlign, _textScaleFactor = textScaleFactor, _maxLines = maxLines, _ellipsis = ellipsis; ui.ParagraphStyle? _paragraphStyle; + final MongolTextAlign _textAlign; final double _textScaleFactor; final int? _maxLines; final String? _ellipsis; @@ -706,6 +742,7 @@ class MongolParagraphBuilder { _plainText.toString(), _maxLines, _ellipsisRun(style), + _textAlign, ); } diff --git a/lib/src/base/mongol_text_align.dart b/lib/src/base/mongol_text_align.dart new file mode 100644 index 0000000..e621d76 --- /dev/null +++ b/lib/src/base/mongol_text_align.dart @@ -0,0 +1,27 @@ +// Copyright 2014 The Flutter Authors. +// Copyright 2021 Suragch. +// All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// Whether and how to align text vertically. +/// +/// This is only used at the MongolTextPainter level and above. Below that the +/// more primitive [TextAlign] enum is used and top is mapped to left and +/// bottom is mapped to right. +enum MongolTextAlign { + /// Align the text on the top edge of the container. + top, + + /// Align the text on the bottom edge of the container. + bottom, + + /// Align the text in the center of the container. + center, + + /// Stretch lines of text that end with a soft line break to fill the height + /// of the container. + /// + /// Lines that end with hard line breaks are aligned towards the [top] edge. + justify, +} \ No newline at end of file diff --git a/lib/src/base/mongol_text_painter.dart b/lib/src/base/mongol_text_painter.dart index ee84642..e237058 100644 --- a/lib/src/base/mongol_text_painter.dart +++ b/lib/src/base/mongol_text_painter.dart @@ -10,6 +10,8 @@ import 'dart:ui' as ui show ParagraphStyle; import 'package:flutter/widgets.dart'; import 'package:mongol/src/base/mongol_paragraph.dart'; +import 'mongol_text_align.dart'; + // The default font size if none is specified. This should be kept in // sync with the default values in text_style.dart, as well as the // defaults set in the engine (eg, LibTxt's text_style.h, paragraph_style.h). @@ -31,28 +33,6 @@ class _CaretMetrics { final double? fullWidth; } -/// Whether and how to align text vertically. -/// -/// This is only used at the MongolTextPainter level and above. Below that the -/// more primitive [TextAlign] enum is used and top is mapped to left and -/// bottom is mapped to right. -enum MongolTextAlign { - /// Align the text on the top edge of the container. - top, - - /// Align the text on the bottom edge of the container. - bottom, - - /// Align the text in the center of the container. - center, - - /// Stretch lines of text that end with a soft line break to fill the height - /// of the container. - /// - /// Lines that end with hard line breaks are aligned towards the [top] edge. - justify, -} - /// A convenience method for converting MongolTextAlign to TextAlign TextAlign mapMongolToHorizontalTextAlign(MongolTextAlign textAlign) { switch (textAlign) { @@ -383,6 +363,7 @@ class MongolTextPainter { if (_paragraph == null) { final builder = MongolParagraphBuilder( _createParagraphStyle(), + textAlign: _textAlign, textScaleFactor: _textScaleFactor, maxLines: _maxLines, ellipsis: _ellipsis, diff --git a/lib/src/editing/mongol_editable_text.dart b/lib/src/editing/mongol_editable_text.dart index 6bec157..838fbaa 100644 --- a/lib/src/editing/mongol_editable_text.dart +++ b/lib/src/editing/mongol_editable_text.dart @@ -15,6 +15,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart' show SchedulerBinding; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' hide EditableText, EditableTextState; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/editing/mongol_render_editable.dart'; import 'package:mongol/src/base/mongol_text_painter.dart'; diff --git a/lib/src/editing/mongol_input_decorator.dart b/lib/src/editing/mongol_input_decorator.dart index d9e001a..b7cd0ce 100644 --- a/lib/src/editing/mongol_input_decorator.dart +++ b/lib/src/editing/mongol_input_decorator.dart @@ -24,8 +24,8 @@ import 'package:flutter/material.dart' import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import '../base/mongol_text_align.dart'; import '../text/mongol_text.dart'; -import '../base/mongol_text_painter.dart'; import 'alignment.dart'; import 'input_border.dart'; diff --git a/lib/src/editing/mongol_render_editable.dart b/lib/src/editing/mongol_render_editable.dart index 7482759..fe94b10 100644 --- a/lib/src/editing/mongol_render_editable.dart +++ b/lib/src/editing/mongol_render_editable.dart @@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/base/mongol_text_painter.dart'; const double _kCaretGap = 1.0; // pixels diff --git a/lib/src/editing/mongol_text_field.dart b/lib/src/editing/mongol_text_field.dart index 63c1436..8b115c2 100644 --- a/lib/src/editing/mongol_text_field.dart +++ b/lib/src/editing/mongol_text_field.dart @@ -26,6 +26,7 @@ import 'package:flutter/material.dart' MaterialStateProperty, MaterialStateMouseCursor, MaterialState; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'alignment.dart'; import 'mongol_editable_text.dart'; diff --git a/lib/src/text/mongol_render_paragraph.dart b/lib/src/text/mongol_render_paragraph.dart index f2dff2b..053447c 100644 --- a/lib/src/text/mongol_render_paragraph.dart +++ b/lib/src/text/mongol_render_paragraph.dart @@ -9,6 +9,7 @@ import 'dart:ui' as ui show Gradient, Shader; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; import '../base/mongol_text_painter.dart'; diff --git a/lib/src/text/mongol_rich_text.dart b/lib/src/text/mongol_rich_text.dart index 7a03170..cad2dc0 100644 --- a/lib/src/text/mongol_rich_text.dart +++ b/lib/src/text/mongol_rich_text.dart @@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'mongol_render_paragraph.dart'; -import '../base/mongol_text_painter.dart'; +import '../base/mongol_text_align.dart'; /// A string of rich text in vertical Mongolian layout. /// diff --git a/lib/src/text/mongol_text.dart b/lib/src/text/mongol_text.dart index 322c598..34637eb 100644 --- a/lib/src/text/mongol_text.dart +++ b/lib/src/text/mongol_text.dart @@ -9,6 +9,7 @@ import 'package:flutter/widgets.dart'; import 'mongol_rich_text.dart'; import '../base/mongol_text_painter.dart'; +import '../base/mongol_text_align.dart'; /// A run of vertical text with a single style. /// diff --git a/pubspec.yaml b/pubspec.yaml index efdc29d..391dfcb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mongol description: Flutter widget package for displaying and editing vertical Mongolian text. -version: 1.0.0 +version: 1.1.0 homepage: https://github.com/suragch/mongol environment: diff --git a/test/mongol_editable_text_test.dart b/test/mongol_editable_text_test.dart index 58d9243..b54ab2c 100644 --- a/test/mongol_editable_text_test.dart +++ b/test/mongol_editable_text_test.dart @@ -15,6 +15,7 @@ import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart' hide Finder; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/editing/mongol_editable_text.dart'; import 'package:mongol/src/editing/mongol_render_editable.dart'; import 'package:mongol/src/base/mongol_text_painter.dart'; diff --git a/test/mongol_paragraph_test.dart b/test/mongol_paragraph_test.dart index 2eb7f46..9ef7d6c 100644 --- a/test/mongol_paragraph_test.dart +++ b/test/mongol_paragraph_test.dart @@ -3,17 +3,23 @@ import 'dart:ui'; import 'package:flutter_test/flutter_test.dart'; import 'package:mongol/src/base/mongol_paragraph.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; void main() { MongolParagraph _getParagraph( String text, double height, { + MongolTextAlign? textAlign, int? maxLines, String? ellipsis, }) { final paragraphStyle = ui.ParagraphStyle(ellipsis: ellipsis); - final paragraphBuilder = MongolParagraphBuilder(paragraphStyle, - maxLines: maxLines, ellipsis: ellipsis); + final paragraphBuilder = MongolParagraphBuilder( + paragraphStyle, + textAlign: textAlign ?? MongolTextAlign.top, + maxLines: maxLines, + ellipsis: ellipsis, + ); paragraphBuilder.addText(text); final constraints = MongolParagraphConstraints(height: height); final paragraph = paragraphBuilder.build(); @@ -462,7 +468,7 @@ void main() { expect(width, 42); }); - // // temporarily deleting this test. The ellipsis currently doesn't + // // temporarily deleting this test. The ellipsis currently doesn't // // affect the intrinsic size. // test('last run has ellipsis when exceeding max lines', () { // const text = 'this is some long text that should break over 3 lines'; diff --git a/test/mongol_render_editable_test.dart b/test/mongol_render_editable_test.dart index 45472cf..d693a44 100644 --- a/test/mongol_render_editable_test.dart +++ b/test/mongol_render_editable_test.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' as rendering; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/editing/mongol_render_editable.dart'; import 'package:mongol/src/base/mongol_text_painter.dart'; diff --git a/test/mongol_text_field_test.dart b/test/mongol_text_field_test.dart index 88c8dae..75972ac 100644 --- a/test/mongol_text_field_test.dart +++ b/test/mongol_text_field_test.dart @@ -17,6 +17,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/foundation.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/editing/mongol_editable_text.dart'; import 'package:mongol/src/editing/mongol_input_decorator.dart'; import 'package:mongol/src/editing/mongol_render_editable.dart'; diff --git a/test/mongol_text_painter_test.dart b/test/mongol_text_painter_test.dart index e52c332..c0305b2 100644 --- a/test/mongol_text_painter_test.dart +++ b/test/mongol_text_painter_test.dart @@ -9,6 +9,7 @@ import 'dart:ui' as ui; import 'package:flutter/painting.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mongol/src/base/mongol_text_align.dart'; import 'package:mongol/src/base/mongol_text_painter.dart'; void main() {