#381 fixed inconsistent background height for multi-script subtitles

This commit is contained in:
Thibault Deckers 2022-11-05 15:45:09 +01:00
parent 22149ffca2
commit 15c225fa89
5 changed files with 107 additions and 13 deletions

View file

@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
- rendering of panoramas with inconsistent metadata
- failing scan of items copied to SD card on older devices
- 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

View 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,
],
);
},
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/utils/constants.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/fx/borders.dart';
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
@ -12,8 +13,13 @@ class SubtitleSample extends StatelessWidget {
@override
Widget build(BuildContext context) {
final textSpans = [
TextSpan(text: context.l10n.settingsSubtitleThemeSample),
];
return Consumer<Settings>(
builder: (context, settings, child) {
final textAlign = settings.subtitleTextAlignment;
final outlineColor = Colors.black.withOpacity(settings.subtitleTextColor.opacity);
final shadows = [
Shadow(
@ -34,7 +40,7 @@ class SubtitleSample extends StatelessWidget {
),
height: 128,
child: AnimatedAlign(
alignment: _getAlignment(settings.subtitleTextAlignment),
alignment: _getAlignment(textAlign),
curve: Curves.easeInOutCubic,
duration: const Duration(milliseconds: 400),
child: Padding(
@ -42,20 +48,24 @@ class SubtitleSample extends StatelessWidget {
child: AnimatedDefaultTextStyle(
style: TextStyle(
color: settings.subtitleTextColor,
backgroundColor: settings.subtitleBackgroundColor,
fontSize: settings.subtitleFontSize,
shadows: settings.subtitleShowOutline ? shadows : null,
),
textAlign: settings.subtitleTextAlignment,
textAlign: textAlign,
duration: const Duration(milliseconds: 200),
child: OutlinedText(
textSpans: [
TextSpan(
text: context.l10n.settingsSubtitleThemeSample,
child: Builder(
builder: (context) => TextBackgroundPainter(
spans: textSpans,
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,
),
),
),
),

View file

@ -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) {
final match = alphaPattern.firstMatch(param);

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/settings.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/visual/state.dart';
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
@ -42,7 +43,6 @@ class VideoSubtitles extends StatelessWidget {
];
final baseStyle = TextStyle(
color: settings.subtitleTextColor,
backgroundColor: settings.subtitleBackgroundColor,
fontSize: settings.subtitleFontSize,
shadows: settings.subtitleShowOutline ? baseShadows : null,
);
@ -243,7 +243,14 @@ class VideoSubtitles extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Align(
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,
),
),
);
}