diff --git a/lib/src/css_box_widget.dart b/lib/src/css_box_widget.dart index 77d545d022..2537a4edc2 100644 --- a/lib/src/css_box_widget.dart +++ b/lib/src/css_box_widget.dart @@ -73,6 +73,7 @@ class CssBoxWidget extends StatelessWidget { children: [ Container( decoration: BoxDecoration( + borderRadius: style.borderRadius?.toBorderRadius() ?? BorderRadius.zero, border: style.border, color: style.backgroundColor, //Colors the padding and content boxes ), diff --git a/lib/src/css_parser.dart b/lib/src/css_parser.dart index 02cbe8a3ae..da58961897 100644 --- a/lib/src/css_parser.dart +++ b/lib/src/css_parser.dart @@ -3,6 +3,7 @@ import 'package:csslib/visitor.dart' as css; import 'package:csslib/parser.dart' as cssparser; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_html/src/style/radius.dart'; import 'package:flutter_html/src/utils.dart'; //TODO refactor @@ -291,6 +292,49 @@ Style declarationsToStyle(Map> declarations) { ); style.border = newBorder; break; + case 'border-radius': + List? borderRadiuses = + value.whereType().toList(); + + /// List might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping] + borderRadiuses.removeWhere((element) => + element == null || + (element is! css.LengthTerm && + element is! css.PercentageTerm && + element is! css.EmTerm && + element is! css.RemTerm && + element is! css.NumberTerm)); + css.LiteralTerm? borderRadius = + borderRadiuses.firstWhereOrNull((element) => element != null); + final newBorderRadius = ExpressionMapping.expressionToBorderRadius(borderRadius); + HtmlRadii newBorderRadii = HtmlRadii.all(newBorderRadius.value, newBorderRadius.unit); + style.borderRadius = newBorderRadii; + break; + case 'border-top-left-radius': + case 'border-top-right-radius': + case 'border-bottom-left-radius': + case 'border-bottom-right-radius': + List? borderRadiuses = + value.whereType().toList(); + + /// List might include other values than the ones we want for [BorderSide.width], so make sure to remove those before passing it to [ExpressionMapping] + borderRadiuses.removeWhere((element) => + element == null || + (element is! css.LengthTerm && + element is! css.PercentageTerm && + element is! css.EmTerm && + element is! css.RemTerm && + element is! css.NumberTerm)); + css.LiteralTerm? borderRadius = + borderRadiuses.firstWhereOrNull((element) => element != null); + HtmlRadii newBorder = HtmlRadii( + topLeft: property == 'border-top-left-radius' ? ExpressionMapping.expressionToBorderRadius(borderRadius) : (style.borderRadius?.topLeft ?? HtmlRadius.zero()), + topRight: property == 'border-top-right-radius' ? ExpressionMapping.expressionToBorderRadius(borderRadius) : (style.borderRadius?.topRight ?? HtmlRadius.zero()), + bottomLeft: property == 'border-bottom-left-radius' ? ExpressionMapping.expressionToBorderRadius(borderRadius) : (style.borderRadius?.bottomLeft ?? HtmlRadius.zero()), + bottomRight: property == 'border-bottom-right-radius' ? ExpressionMapping.expressionToBorderRadius(borderRadius) : (style.borderRadius?.bottomRight ?? HtmlRadius.zero()), + ); + style.borderRadius = newBorder; + break; case 'color': style.color = style.textDecorationColor = ExpressionMapping.expressionToColor(value.first) ?? style.color; @@ -890,6 +934,14 @@ class ExpressionMapping { return null; } + static HtmlRadius expressionToBorderRadius(css.Expression? value) { + if (value == null) { + return HtmlRadius.zero(); + } + final computedValue = expressionToLengthOrPercent(value); + return HtmlRadius(computedValue.value, computedValue.unit); + } + static TextDirection expressionToDirection(css.Expression value) { if (value is css.LiteralTerm) { switch (value.text) { @@ -1345,23 +1397,21 @@ class ExpressionMapping { shadow.add(Shadow( color: expressionToColor(color)!, offset: Offset( - double.tryParse((offsetX).text.replaceAll(nonNumberRegex, ''))!, - double.tryParse( - (offsetY).text.replaceAll(nonNumberRegex, ''))!), + double.tryParse((offsetX).text.replaceAll(nonNumberRegex, '')) ?? 0.0, + double.tryParse((offsetY).text.replaceAll(nonNumberRegex, '')) ?? 0.0 + ), blurRadius: (blurRadius is css.LiteralTerm) - ? double.tryParse( - (blurRadius).text.replaceAll(nonNumberRegex, ''))! + ? double.tryParse((blurRadius).text.replaceAll(nonNumberRegex, '')) ?? 0.0 : 0.0, )); } else { shadow.add(Shadow( offset: Offset( - double.tryParse((offsetX).text.replaceAll(nonNumberRegex, ''))!, - double.tryParse( - (offsetY).text.replaceAll(nonNumberRegex, ''))!), + double.tryParse((offsetX).text.replaceAll(nonNumberRegex, '')) ?? 0.0, + double.tryParse((offsetY).text.replaceAll(nonNumberRegex, '')) ?? 0.0 + ), blurRadius: (blurRadius is css.LiteralTerm) - ? double.tryParse( - (blurRadius).text.replaceAll(nonNumberRegex, ''))! + ? double.tryParse((blurRadius).text.replaceAll(nonNumberRegex, '')) ?? 0.0 : 0.0, )); } diff --git a/lib/src/style.dart b/lib/src/style.dart index 014f7ff91f..d22106f7c0 100644 --- a/lib/src/style.dart +++ b/lib/src/style.dart @@ -11,6 +11,7 @@ export 'package:flutter_html/src/style/size.dart'; export 'package:flutter_html/src/style/fontsize.dart'; export 'package:flutter_html/src/style/lineheight.dart'; export 'package:flutter_html/src/style/marker.dart'; +export 'package:flutter_html/src/style/radius.dart'; ///This class represents all the available CSS attributes ///for this package. @@ -213,6 +214,7 @@ class Style { String? after; Border? border; Alignment? alignment; + HtmlRadii? borderRadius; /// MaxLine /// @@ -271,6 +273,7 @@ class Style { this.before, this.after, this.border, + this.borderRadius, this.alignment, this.maxLines, this.textOverflow, @@ -303,7 +306,7 @@ class Style { TextStyle generateTextStyle() { return TextStyle( - backgroundColor: (display?.isBlock ?? false) ? null : backgroundColor, + backgroundColor: ((display?.isBlock ?? false) || display == Display.inlineBlock) ? null : backgroundColor, color: color, decoration: textDecoration, decorationColor: textDecorationColor, @@ -363,6 +366,7 @@ class Style { before: other.before, after: other.after, border: border?.merge(other.border) ?? other.border, + borderRadius: borderRadius ?? other.borderRadius, alignment: other.alignment, maxLines: other.maxLines, textOverflow: other.textOverflow, @@ -452,6 +456,7 @@ class Style { String? before, String? after, Border? border, + HtmlRadii? borderRadius, Alignment? alignment, Widget? markerContent, int? maxLines, @@ -495,6 +500,7 @@ class Style { before: beforeAfterNull == true ? null : before ?? this.before, after: beforeAfterNull == true ? null : after ?? this.after, border: border ?? this.border, + borderRadius: borderRadius ?? this.borderRadius, alignment: alignment ?? this.alignment, maxLines: maxLines ?? this.maxLines, textOverflow: textOverflow ?? this.textOverflow, @@ -563,6 +569,13 @@ class Style { blockStart: padding?.blockStart?.getRelativeValue(remValue, emValue), blockEnd: padding?.blockEnd?.getRelativeValue(remValue, emValue), ); + + borderRadius = borderRadius?.copyWith( + topLeft: borderRadius?.topLeft?.getRelativeValue(remValue, emValue), + topRight: borderRadius?.topRight?.getRelativeValue(remValue, emValue), + bottomLeft: borderRadius?.bottomLeft?.getRelativeValue(remValue, emValue), + bottomRight: borderRadius?.bottomRight?.getRelativeValue(remValue, emValue), + ); } } @@ -599,6 +612,17 @@ extension MergeBorders on Border? { } } +extension _RadiusRelativeValues on HtmlRadius { + HtmlRadius? getRelativeValue(double remValue, double emValue) { + double? calculatedValue = calculateRelativeValue(remValue, emValue); + if (calculatedValue != null) { + return HtmlRadius(calculatedValue); + } + + return null; + } +} + enum ListStyleType { arabicIndic('arabic-indic'), armenian('armenian'), diff --git a/lib/src/style/radius.dart b/lib/src/style/radius.dart new file mode 100644 index 0000000000..f64034beed --- /dev/null +++ b/lib/src/style/radius.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/src/style/length.dart'; + +class HtmlRadius extends LengthOrPercent { + HtmlRadius(double value, [Unit? unit = Unit.px]) + : assert(value >= 0, "Radius must be non-negative"), + super(value, unit ?? Unit.px); + + HtmlRadius.zero() : super(0, Unit.px); + + @override + int get hashCode => Object.hash(value, unit); + + @override + bool operator ==(Object other) { + return other is HtmlRadius && other.value == value && other.unit == unit; + } +} + +class HtmlRadii { + final HtmlRadius? topLeft; + final HtmlRadius? topRight; + final HtmlRadius? bottomLeft; + final HtmlRadius? bottomRight; + + const HtmlRadii({ + this.topLeft, + this.topRight, + this.bottomLeft, + this.bottomRight, + }); + + /// 类似 EdgeInsets.zero + static HtmlRadii get zero => HtmlRadii.all(0); + + /// 类似 EdgeInsets.all + HtmlRadii.all(double value, [Unit? unit]) + : topLeft = HtmlRadius(value, unit), + topRight = HtmlRadius(value, unit), + bottomLeft = HtmlRadius(value, unit), + bottomRight = HtmlRadius(value, unit); + + /// 类似 EdgeInsets.only + HtmlRadii.only({ + double? topLeft, + double? topRight, + double? bottomLeft, + double? bottomRight, + Unit? unit, + }) : topLeft = topLeft != null ? HtmlRadius(topLeft, unit) : null, + topRight = topRight != null ? HtmlRadius(topRight, unit) : null, + bottomLeft = bottomLeft != null ? HtmlRadius(bottomLeft, unit) : null, + bottomRight = bottomRight != null ? HtmlRadius(bottomRight, unit) : null; + + HtmlRadii copyWith({ + HtmlRadius? topLeft, + HtmlRadius? topRight, + HtmlRadius? bottomLeft, + HtmlRadius? bottomRight, + }) { + return HtmlRadii( + topLeft: topLeft ?? this.topLeft, + topRight: topRight ?? this.topRight, + bottomLeft: bottomLeft ?? this.bottomLeft, + bottomRight: bottomRight ?? this.bottomRight, + ); + } + + @override + int get hashCode => + Object.hash(topLeft, topRight, bottomLeft, bottomRight); + + @override + bool operator ==(Object other) { + return other is HtmlRadii && + topLeft == other.topLeft && + topRight == other.topRight && + bottomLeft == other.bottomLeft && + bottomRight == other.bottomRight; + } + + BorderRadius toBorderRadius() { + return BorderRadius.only( + topLeft: Radius.circular(topLeft?.value ?? 0), + topRight: Radius.circular(topRight?.value ?? 0), + bottomLeft: Radius.circular(bottomLeft?.value ?? 0), + bottomRight: Radius.circular(bottomRight?.value ?? 0), + ); + } +}