#381 fixed inconsistent background height for multi-script subtitles
This commit is contained in:
parent
22149ffca2
commit
15c225fa89
5 changed files with 107 additions and 13 deletions
|
@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- rendering of panoramas with inconsistent metadata
|
- rendering of panoramas with inconsistent metadata
|
||||||
- failing scan of items copied to SD card on older devices
|
- failing scan of items copied to SD card on older devices
|
||||||
- unreplaceable covers set before v1.7.1
|
- unreplaceable covers set before v1.7.1
|
||||||
|
- inconsistent background height for multi-script subtitles
|
||||||
|
|
||||||
## <a id="v1.7.1"></a>[v1.7.1] - 2022-10-09
|
## <a id="v1.7.1"></a>[v1.7.1] - 2022-10-09
|
||||||
|
|
||||||
|
|
76
lib/widgets/common/basic/text_background_painter.dart
Normal file
76
lib/widgets/common/basic/text_background_painter.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
|
// as of Flutter v3.3.7, text style background does not have consistent height
|
||||||
|
// when rendering multi-script text, so we paint the background behind via a stack instead
|
||||||
|
class TextBackgroundPainter extends StatelessWidget {
|
||||||
|
final List<TextSpan> spans;
|
||||||
|
final TextStyle style;
|
||||||
|
final TextAlign textAlign;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const TextBackgroundPainter({
|
||||||
|
super.key,
|
||||||
|
required this.spans,
|
||||||
|
required this.style,
|
||||||
|
required this.textAlign,
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final backgroundColor = style.backgroundColor;
|
||||||
|
if (backgroundColor == null || backgroundColor.alpha == 0) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final paragraph = RenderParagraph(
|
||||||
|
TextSpan(
|
||||||
|
children: spans,
|
||||||
|
style: style,
|
||||||
|
),
|
||||||
|
textAlign: textAlign,
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
textScaleFactor: MediaQuery.textScaleFactorOf(context),
|
||||||
|
)..layout(constraints, parentUsesSize: true);
|
||||||
|
|
||||||
|
final textLength = spans.map((v) => v.text?.length ?? 0).sum;
|
||||||
|
final allBoxes = paragraph.getBoxesForSelection(
|
||||||
|
TextSelection(baseOffset: 0, extentOffset: textLength),
|
||||||
|
boxHeightStyle: ui.BoxHeightStyle.max,
|
||||||
|
);
|
||||||
|
|
||||||
|
// merge boxes to avoid artifacts at box edges, from anti-aliasing and rounding hacks
|
||||||
|
final lineRects = groupBy<TextBox, double>(allBoxes, (v) => v.top).entries.map((kv) {
|
||||||
|
final top = kv.key;
|
||||||
|
final lineBoxes = kv.value;
|
||||||
|
return Rect.fromLTRB(
|
||||||
|
lineBoxes.map((v) => v.left).min,
|
||||||
|
top,
|
||||||
|
lineBoxes.map((v) => v.right).max,
|
||||||
|
lineBoxes.first.bottom,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
...lineRects.map((rect) {
|
||||||
|
return Positioned.fromRect(
|
||||||
|
rect: rect,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_background_painter.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/borders.dart';
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
||||||
|
@ -12,8 +13,13 @@ class SubtitleSample extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final textSpans = [
|
||||||
|
TextSpan(text: context.l10n.settingsSubtitleThemeSample),
|
||||||
|
];
|
||||||
|
|
||||||
return Consumer<Settings>(
|
return Consumer<Settings>(
|
||||||
builder: (context, settings, child) {
|
builder: (context, settings, child) {
|
||||||
|
final textAlign = settings.subtitleTextAlignment;
|
||||||
final outlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity);
|
final outlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity);
|
||||||
final shadows = [
|
final shadows = [
|
||||||
Shadow(
|
Shadow(
|
||||||
|
@ -34,7 +40,7 @@ class SubtitleSample extends StatelessWidget {
|
||||||
),
|
),
|
||||||
height: 128,
|
height: 128,
|
||||||
child: AnimatedAlign(
|
child: AnimatedAlign(
|
||||||
alignment: _getAlignment(settings.subtitleTextAlignment),
|
alignment: _getAlignment(textAlign),
|
||||||
curve: Curves.easeInOutCubic,
|
curve: Curves.easeInOutCubic,
|
||||||
duration: const Duration(milliseconds: 400),
|
duration: const Duration(milliseconds: 400),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -42,20 +48,24 @@ class SubtitleSample extends StatelessWidget {
|
||||||
child: AnimatedDefaultTextStyle(
|
child: AnimatedDefaultTextStyle(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: settings.subtitleTextColor,
|
color: settings.subtitleTextColor,
|
||||||
backgroundColor: settings.subtitleBackgroundColor,
|
|
||||||
fontSize: settings.subtitleFontSize,
|
fontSize: settings.subtitleFontSize,
|
||||||
shadows: settings.subtitleShowOutline ? shadows : null,
|
shadows: settings.subtitleShowOutline ? shadows : null,
|
||||||
),
|
),
|
||||||
textAlign: settings.subtitleTextAlignment,
|
textAlign: textAlign,
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
child: OutlinedText(
|
child: Builder(
|
||||||
textSpans: [
|
builder: (context) => TextBackgroundPainter(
|
||||||
TextSpan(
|
spans: textSpans,
|
||||||
text: context.l10n.settingsSubtitleThemeSample,
|
style: DefaultTextStyle.of(context).style.copyWith(
|
||||||
|
backgroundColor: settings.subtitleBackgroundColor,
|
||||||
|
),
|
||||||
|
textAlign: textAlign,
|
||||||
|
child: OutlinedText(
|
||||||
|
textSpans: textSpans,
|
||||||
|
outlineWidth: settings.subtitleShowOutline ? 1 : 0,
|
||||||
|
outlineColor: outlineColor,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
outlineWidth: settings.subtitleShowOutline ? 1 : 0,
|
|
||||||
outlineColor: outlineColor,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -389,7 +389,7 @@ class AssParser {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _replaceChars(String text) => text.replaceAll(r'\h', noBreakSpace).replaceAll(r'\N', '\n');
|
static String _replaceChars(String text) => text.replaceAll(r'\h', noBreakSpace).replaceAll(r'\N', '\n').trim();
|
||||||
|
|
||||||
static int? _parseAlpha(String param) {
|
static int? _parseAlpha(String param) {
|
||||||
final match = alphaPattern.firstMatch(param);
|
final match = alphaPattern.firstMatch(param);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_background_painter.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/state.dart';
|
import 'package:aves/widgets/viewer/visual/state.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
|
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
|
||||||
|
@ -42,7 +43,6 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
];
|
];
|
||||||
final baseStyle = TextStyle(
|
final baseStyle = TextStyle(
|
||||||
color: settings.subtitleTextColor,
|
color: settings.subtitleTextColor,
|
||||||
backgroundColor: settings.subtitleBackgroundColor,
|
|
||||||
fontSize: settings.subtitleFontSize,
|
fontSize: settings.subtitleFontSize,
|
||||||
shadows: settings.subtitleShowOutline ? baseShadows : null,
|
shadows: settings.subtitleShowOutline ? baseShadows : null,
|
||||||
);
|
);
|
||||||
|
@ -243,7 +243,14 @@ class VideoSubtitles extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment(alignX, alignY),
|
alignment: Alignment(alignX, alignY),
|
||||||
child: child,
|
child: TextBackgroundPainter(
|
||||||
|
spans: spans,
|
||||||
|
style: DefaultTextStyle.of(context).style.merge(spans.first.style!.copyWith(
|
||||||
|
backgroundColor: settings.subtitleBackgroundColor,
|
||||||
|
)),
|
||||||
|
textAlign: textAlign,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue