diff --git a/lib/widgets/viewer/visual/subtitle/ass_parser.dart b/lib/widgets/viewer/visual/subtitle/ass_parser.dart index 0f1a8a473..2a0c62812 100644 --- a/lib/widgets/viewer/visual/subtitle/ass_parser.dart +++ b/lib/widgets/viewer/visual/subtitle/ass_parser.dart @@ -4,7 +4,7 @@ import 'package:aves/widgets/viewer/visual/subtitle/style.dart'; import 'package:flutter/material.dart'; class AssParser { - static final tagPattern = RegExp(r'(' + static final tagPattern = RegExp(r'\*?(' r'1a|2a|3a|4a' r'|1c|2c|3c|4c' r'|alpha|an|a' @@ -31,8 +31,8 @@ class AssParser { // (,) static final multiParamPattern = RegExp('\\((.*)\\)'); - // e.g. m 50 0 b 100 0 100 100 50 100 b 0 100 0 0 50 0 - static final pathPattern = RegExp(r'([mnlbspc])([\s\d]+)'); + // e.g. m 937.5 472.67 b 937.5 472.67 937.25 501.25 960 501.5 960 501.5 937.5 500.33 937.5 529.83 + static final pathPattern = RegExp(r'([mnlbspc])([.\s\d]+)'); static const noBreakSpace = '\u00A0'; @@ -50,9 +50,10 @@ class AssParser { final matches = RegExp(r'{(.*?)}').allMatches(text); matches.forEach((m) { if (i != m.start) { + final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start)); spans.add(StyledSubtitleSpan( textSpan: TextSpan( - text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i, m.start)), + text: spanText, style: textStyle, ), extraStyle: extraStyle, @@ -230,13 +231,14 @@ class AssParser { // \p drawing paths final scale = int.tryParse(param); if (scale != null) { - extraStyle = extraStyle.copyWith( - drawingPaths: scale > 0 - ? _parsePaths( - text.substring(m.end), - scale, - ) - : null); + if (scale > 0) { + final start = m.end; + final end = text.indexOf('{', start); + final commands = text.substring(start, end == -1 ? null : end); + extraStyle = extraStyle.copyWith(drawingPaths: _parsePaths(commands, scale)); + } else { + extraStyle = extraStyle.copyWith(drawingPaths: null); + } } break; } @@ -307,9 +309,10 @@ class AssParser { }); }); if (i != text.length) { + final spanText = extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i)); spans.add(StyledSubtitleSpan( textSpan: TextSpan( - text: extraStyle.drawingPaths?.isNotEmpty == true ? null : _replaceChars(text.substring(i)), + text: spanText, style: textStyle, ), extraStyle: extraStyle, diff --git a/lib/widgets/viewer/visual/subtitle/style.dart b/lib/widgets/viewer/visual/subtitle/style.dart index 478d47405..af9a19948 100644 --- a/lib/widgets/viewer/visual/subtitle/style.dart +++ b/lib/widgets/viewer/visual/subtitle/style.dart @@ -9,11 +9,11 @@ class SubtitleStyle with Diagnosticable { final double? borderWidth, edgeBlur, rotationX, rotationY, rotationZ, scaleX, scaleY, shearX, shearY; final List? drawingPaths; - bool get rotating => (rotationX ?? 0) > 0 || (rotationY ?? 0) > 0 || (rotationZ ?? 0) > 0; + bool get rotating => (rotationX ?? 0) != 0 || (rotationY ?? 0) != 0 || (rotationZ ?? 0) != 0; - bool get scaling => (scaleX ?? 0) > 0 || (scaleY ?? 0) > 0; + bool get scaling => (scaleX ?? 1) != 1 || (scaleY ?? 1) != 1; - bool get shearing => (shearX ?? 0) > 0 || (shearY ?? 0) > 0; + bool get shearing => (shearX ?? 0) != 0 || (shearY ?? 0) != 0; const SubtitleStyle({ this.hAlign, diff --git a/lib/widgets/viewer/visual/subtitle/subtitle.dart b/lib/widgets/viewer/visual/subtitle/subtitle.dart index 9896a3664..b219c575a 100644 --- a/lib/widgets/viewer/visual/subtitle/subtitle.dart +++ b/lib/widgets/viewer/visual/subtitle/subtitle.dart @@ -73,11 +73,20 @@ class VideoSubtitles extends StatelessWidget { if (text == null) return const SizedBox(); if (debugMode) { - return Center( - child: OutlinedText( - textSpans: [TextSpan(text: text)], - outlineWidth: 1, - outlineColor: Colors.black, + return Padding( + padding: const EdgeInsets.only(top: 100.0), + child: Align( + alignment: Alignment.topLeft, + child: OutlinedText( + textSpans: [ + TextSpan( + text: text, + style: const TextStyle(fontSize: 14), + ) + ], + outlineWidth: 1, + outlineColor: Colors.black, + ), ), ); } @@ -90,26 +99,45 @@ class VideoSubtitles extends StatelessWidget { return Stack( children: byExtraStyle.entries.map((kv) { final extraStyle = kv.key; - final spans = kv.value.map((v) => v.textSpan).toList(); + final spans = kv.value.map((v) { + final span = v.textSpan; + final style = span.style; + return position != null && style != null + ? TextSpan( + text: span.text, + style: style.copyWith( + shadows: style.shadows + ?.map((v) => Shadow( + color: v.color, + offset: v.offset * viewScale, + blurRadius: v.blurRadius * viewScale, + )) + .toList()), + ) + : span; + }).toList(); final drawingPaths = extraStyle.drawingPaths; - final outlineColor = extraStyle.borderColor ?? Colors.black; - var child = drawingPaths != null - ? CustomPaint( - painter: SubtitlePathPainter( - paths: drawingPaths, - scale: viewScale, - fillColor: spans.firstOrNull?.style?.color ?? Colors.white, - strokeColor: outlineColor, - ), - ) - : OutlinedText( - textSpans: spans, - outlineWidth: extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1), - outlineColor: outlineColor, - outlineBlurSigma: extraStyle.edgeBlur ?? 0, - textAlign: extraStyle.hAlign ?? TextAlign.center, - ); + Widget child; + if (drawingPaths != null) { + child = CustomPaint( + painter: SubtitlePathPainter( + paths: drawingPaths, + scale: viewScale, + fillColor: spans.firstOrNull?.style?.color ?? Colors.white, + strokeColor: extraStyle.borderColor, + ), + ); + } else { + final outlineWidth = extraStyle.borderWidth ?? (extraStyle.edgeBlur != null ? 2 : 1); + child = OutlinedText( + textSpans: spans, + outlineWidth: outlineWidth * (position != null ? viewScale : 1), + outlineColor: extraStyle.borderColor ?? Colors.black, + outlineBlurSigma: extraStyle.edgeBlur ?? 0, + textAlign: extraStyle.hAlign ?? TextAlign.center, + ); + } var transform = Matrix4.identity(); @@ -220,28 +248,34 @@ class VideoSubtitles extends StatelessWidget { class SubtitlePathPainter extends CustomPainter { final List paths; final double scale; - final Color fillColor, strokeColor; + final Paint? fillPaint, strokePaint; - const SubtitlePathPainter({ + SubtitlePathPainter({ required this.paths, required this.scale, - required this.fillColor, - required this.strokeColor, - }); + required Color? fillColor, + required Color? strokeColor, + }) : fillPaint = fillColor != null + ? (Paint() + ..style = PaintingStyle.fill + ..color = fillColor) + : null, + strokePaint = strokeColor != null + ? (Paint() + ..style = PaintingStyle.stroke + ..color = strokeColor) + : null; @override void paint(Canvas canvas, Size size) { - final fillPaint = Paint() - ..style = PaintingStyle.fill - ..color = fillColor; - final strokePaint = Paint() - ..style = PaintingStyle.stroke - ..color = strokeColor; - canvas.scale(scale, scale); paths.forEach((path) { - canvas.drawPath(path, fillPaint); - canvas.drawPath(path, strokePaint); + if (fillPaint != null) { + canvas.drawPath(path, fillPaint!); + } + if (strokePaint != null) { + canvas.drawPath(path, strokePaint!); + } }); }