From 65845c6e106a08a2dd931090c045e75c6df9421c Mon Sep 17 00:00:00 2001 From: tapframe Date: Thu, 11 Sep 2025 12:21:47 +0530 Subject: [PATCH] Added proper subtitle outline support --- .../player/modals/SubtitleModals.tsx | 19 ++- .../player/subtitles/CustomSubtitles.tsx | 115 ++++++++++++++---- 2 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/components/player/modals/SubtitleModals.tsx b/src/components/player/modals/SubtitleModals.tsx index 5c0c9025..5f2695aa 100644 --- a/src/components/player/modals/SubtitleModals.tsx +++ b/src/components/player/modals/SubtitleModals.tsx @@ -679,7 +679,24 @@ export const SubtitleModals: React.FC = ({ {/* Outline */} Outline - setSubtitleOutline(!subtitleOutline)} style={{ paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10, backgroundColor: subtitleOutline ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center' }}> + { + const next = !subtitleOutline; + setSubtitleOutline(next); + if (next) { + // Apply sensible defaults when enabling outline unless user already set larger values + if (subtitleSize < 24) { + // increase by calling increase handler enough times or provide a direct setter via size controls + // We only have +/- handlers here, so set via stepping until >= 24 + const steps = Math.ceil((24 - subtitleSize) / 1); // size is integer steps + for (let i = 0; i < steps; i++) { + increaseSubtitleSize(); + } + } + if (subtitleBottomOffset < 40) { + setSubtitleBottomOffset(40); + } + } + }} style={{ paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10, backgroundColor: subtitleOutline ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center' }}> {subtitleOutline ? 'On' : 'Off'} diff --git a/src/components/player/subtitles/CustomSubtitles.tsx b/src/components/player/subtitles/CustomSubtitles.tsx index 6f47e8b3..015ec65d 100644 --- a/src/components/player/subtitles/CustomSubtitles.tsx +++ b/src/components/player/subtitles/CustomSubtitles.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { View, Text } from 'react-native'; +import Svg, { Text as SvgText, TSpan } from 'react-native-svg'; import { styles } from '../utils/playerStyles'; interface CustomSubtitlesProps { @@ -45,16 +46,10 @@ export const CustomSubtitles: React.FC = ({ const inverseScale = 1 / zoomScale; const bgColor = subtitleBackground ? `rgba(0, 0, 0, ${Math.min(Math.max(backgroundOpacity, 0), 1)})` : 'transparent'; - // Outline via textShadow for multi-direction pass - const outlineStyle = outline - ? { - textShadowColor: outlineColor, - textShadowOffset: { width: 0, height: 0 }, - textShadowRadius: outlineWidth, - } - : {}; + // When using crisp outline, prefer SVG text with real stroke instead of blur shadow + const useCrispSvgOutline = outline === true; - const shadowStyle = textShadow + const shadowStyle = (textShadow && !useCrispSvgOutline) ? { textShadowColor: 'rgba(0, 0, 0, 0.9)', textShadowOffset: { width: 2, height: 2 }, @@ -62,6 +57,12 @@ export const CustomSubtitles: React.FC = ({ } : {}; + // Prepare content lines + const lines = String(currentSubtitle).split(/\r?\n/); + const displayFontSize = subtitleSize * inverseScale; + const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale; + const svgHeight = Math.max(displayFontSize, lines.length * displayLineHeight); + return ( = ({ styles.customSubtitleWrapper, { backgroundColor: bgColor, - alignSelf: align === 'center' ? 'center' : align === 'left' ? 'flex-start' : 'flex-end', + position: 'relative', + width: '100%', + alignItems: 'center', } ]}> - - {currentSubtitle} - + {useCrispSvgOutline ? ( + // Crisp outline using react-native-svg (stroke under, fill on top) + + {(() => { + const anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end'; + const x = 500; // always compute against center of 0..1000; anchor handles alignment + const baseFontSize = displayFontSize; + const lineHeightPx = displayLineHeight; + const strokeWidth = Math.max(0.5, outlineWidth); + return ( + <> + {/* Stroke layer */} + + {lines.map((line, idx) => ( + + {line} + + ))} + + {/* Fill layer */} + + {lines.map((line, idx) => ( + + {line} + + ))} + + + ); + })()} + + ) : ( + // No outline: use RN Text with (optional) shadow + + {currentSubtitle} + + )} );