mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-20 17:02:02 +00:00
sub alignment changes
This commit is contained in:
parent
a0a138081d
commit
4261891a35
2 changed files with 95 additions and 25 deletions
|
|
@ -3,6 +3,7 @@ import { View, Text } from 'react-native';
|
||||||
import Svg, { Text as SvgText, TSpan } from 'react-native-svg';
|
import Svg, { Text as SvgText, TSpan } from 'react-native-svg';
|
||||||
import { styles } from '../utils/playerStyles';
|
import { styles } from '../utils/playerStyles';
|
||||||
import { SubtitleSegment } from '../utils/playerTypes';
|
import { SubtitleSegment } from '../utils/playerTypes';
|
||||||
|
import { detectRTL } from '../utils/playerUtils';
|
||||||
|
|
||||||
interface CustomSubtitlesProps {
|
interface CustomSubtitlesProps {
|
||||||
useCustomSubtitles: boolean;
|
useCustomSubtitles: boolean;
|
||||||
|
|
@ -77,20 +78,31 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
|
|
||||||
// Prepare content lines
|
// Prepare content lines
|
||||||
const lines = String(currentSubtitle).split(/\r?\n/);
|
const lines = String(currentSubtitle).split(/\r?\n/);
|
||||||
|
|
||||||
|
// Detect RTL for each line
|
||||||
|
const lineRTLStatus = lines.map(line => detectRTL(line));
|
||||||
|
|
||||||
const displayFontSize = subtitleSize * inverseScale;
|
const displayFontSize = subtitleSize * inverseScale;
|
||||||
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
const displayLineHeight = subtitleSize * lineHeightMultiplier * inverseScale;
|
||||||
const svgHeight = lines.length * displayLineHeight;
|
const svgHeight = lines.length * displayLineHeight;
|
||||||
|
|
||||||
// Helper to render formatted segments
|
// Helper to render formatted segments
|
||||||
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string) => {
|
const renderFormattedText = (segments: SubtitleSegment[], lineIndex: number, keyPrefix: string, isRTL?: boolean, customLetterSpacing?: number) => {
|
||||||
if (!segments || segments.length === 0) return null;
|
if (!segments || segments.length === 0) return null;
|
||||||
|
|
||||||
|
// For RTL, use a very small negative letter spacing to stretch words slightly
|
||||||
|
// This helps with proper diacritic spacing while maintaining ligatures
|
||||||
|
const effectiveLetterSpacing = isRTL ? (displayFontSize * -0.02) : (customLetterSpacing ?? letterSpacing);
|
||||||
|
|
||||||
|
// For RTL, adjust text alignment
|
||||||
|
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text key={`${keyPrefix}-line-${lineIndex}`} style={{
|
<Text key={`${keyPrefix}-line-${lineIndex}`} style={{
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
textAlign: align,
|
textAlign: effectiveAlign,
|
||||||
letterSpacing,
|
letterSpacing: effectiveLetterSpacing,
|
||||||
fontSize: displayFontSize,
|
fontSize: displayFontSize,
|
||||||
lineHeight: displayLineHeight,
|
lineHeight: displayLineHeight,
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -139,16 +151,33 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
preserveAspectRatio="xMidYMax meet"
|
preserveAspectRatio="xMidYMax meet"
|
||||||
>
|
>
|
||||||
{(() => {
|
{(() => {
|
||||||
const anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end';
|
// Determine alignment and anchor for RTL or LTR
|
||||||
const x = align === 'center' ? 500 : (align === 'left' ? 0 : 1000);
|
const isRTL = lineRTLStatus[0] || lineRTLStatus.some(status => status);
|
||||||
|
let anchor: 'start' | 'middle' | 'end';
|
||||||
|
let x: number;
|
||||||
|
|
||||||
|
if (isRTL) {
|
||||||
|
// For RTL, always use 'end' anchor to position from right edge
|
||||||
|
anchor = 'end';
|
||||||
|
x = 1000;
|
||||||
|
} else {
|
||||||
|
anchor = align === 'center' ? 'middle' : align === 'left' ? 'start' : 'end';
|
||||||
|
x = align === 'center' ? 500 : (align === 'left' ? 0 : 1000);
|
||||||
|
}
|
||||||
|
|
||||||
const baseFontSize = displayFontSize;
|
const baseFontSize = displayFontSize;
|
||||||
const lineHeightPx = displayLineHeight;
|
const lineHeightPx = displayLineHeight;
|
||||||
const strokeWidth = Math.max(0.5, outlineWidth);
|
const strokeWidth = Math.max(0.5, outlineWidth);
|
||||||
|
// For RTL, use a very small negative letter spacing to stretch words slightly
|
||||||
|
// This helps with proper diacritic spacing while maintaining ligatures
|
||||||
|
const effectiveLetterSpacing = isRTL ? (baseFontSize * -0.02) : letterSpacing;
|
||||||
|
|
||||||
// Position text from bottom up - last line should be at svgHeight - small margin
|
// Position text from bottom up - last line should be at svgHeight - small margin
|
||||||
// Add descender buffer so letters like y/g/p/q/j aren't clipped
|
// Add descender buffer so letters like y/g/p/q/j aren't clipped
|
||||||
const descenderBuffer = baseFontSize * 0.35 + (strokeWidth * 0.5);
|
const descenderBuffer = baseFontSize * 0.35 + (strokeWidth * 0.5);
|
||||||
const lastLineBaselineY = svgHeight - descenderBuffer;
|
const lastLineBaselineY = svgHeight - descenderBuffer;
|
||||||
const startY = lastLineBaselineY - (lines.length - 1) * lineHeightPx;
|
const startY = lastLineBaselineY - (lines.length - 1) * lineHeightPx;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Stroke layer */}
|
{/* Stroke layer */}
|
||||||
|
|
@ -164,7 +193,7 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
strokeMiterlimit={2}
|
strokeMiterlimit={2}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
fontSize={baseFontSize}
|
fontSize={baseFontSize}
|
||||||
letterSpacing={letterSpacing}
|
letterSpacing={effectiveLetterSpacing}
|
||||||
>
|
>
|
||||||
{lines.map((line, idx) => (
|
{lines.map((line, idx) => (
|
||||||
<TSpan key={idx} x={x} dy={idx === 0 ? 0 : lineHeightPx}>
|
<TSpan key={idx} x={x} dy={idx === 0 ? 0 : lineHeightPx}>
|
||||||
|
|
@ -180,7 +209,7 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
fill={textColor}
|
fill={textColor}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
fontSize={baseFontSize}
|
fontSize={baseFontSize}
|
||||||
letterSpacing={letterSpacing}
|
letterSpacing={effectiveLetterSpacing}
|
||||||
>
|
>
|
||||||
{lines.map((line, idx) => (
|
{lines.map((line, idx) => (
|
||||||
<TSpan key={idx} x={x} dy={idx === 0 ? 0 : lineHeightPx}>
|
<TSpan key={idx} x={x} dy={idx === 0 ? 0 : lineHeightPx}>
|
||||||
|
|
@ -196,17 +225,27 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
// No outline: use RN Text with (optional) shadow
|
// No outline: use RN Text with (optional) shadow
|
||||||
formattedSegments && formattedSegments.length > 0 ? (
|
formattedSegments && formattedSegments.length > 0 ? (
|
||||||
// Render formatted segments if available
|
// Render formatted segments if available
|
||||||
formattedSegments.map((lineSegments, lineIdx) =>
|
formattedSegments.map((lineSegments, lineIdx) => {
|
||||||
renderFormattedText(lineSegments, lineIdx, 'formatted')
|
const isLineRTL = lineRTLStatus[lineIdx];
|
||||||
)
|
return renderFormattedText(lineSegments, lineIdx, 'formatted', isLineRTL, letterSpacing);
|
||||||
|
})
|
||||||
) : (
|
) : (
|
||||||
|
(() => {
|
||||||
|
const isRTL = lineRTLStatus.some(status => status);
|
||||||
|
// For RTL, use a very small negative letter spacing to stretch words slightly
|
||||||
|
// This helps with proper diacritic spacing while maintaining ligatures
|
||||||
|
const effectiveLetterSpacing = isRTL ? (subtitleSize * inverseScale * -0.02) : letterSpacing;
|
||||||
|
// For RTL, adjust text alignment
|
||||||
|
const effectiveAlign = isRTL && align === 'left' ? 'right' : (isRTL && align === 'right' ? 'left' : align);
|
||||||
|
|
||||||
|
return (
|
||||||
<Text style={[
|
<Text style={[
|
||||||
styles.customSubtitleText,
|
styles.customSubtitleText,
|
||||||
{
|
{
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
textAlign: align,
|
textAlign: effectiveAlign,
|
||||||
letterSpacing,
|
letterSpacing: effectiveLetterSpacing,
|
||||||
fontSize: subtitleSize * inverseScale,
|
fontSize: subtitleSize * inverseScale,
|
||||||
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
|
lineHeight: subtitleSize * lineHeightMultiplier * inverseScale,
|
||||||
transform: [{ scale: inverseScale }],
|
transform: [{ scale: inverseScale }],
|
||||||
|
|
@ -215,6 +254,8 @@ export const CustomSubtitles: React.FC<CustomSubtitlesProps> = ({
|
||||||
]}>
|
]}>
|
||||||
{currentSubtitle}
|
{currentSubtitle}
|
||||||
</Text>
|
</Text>
|
||||||
|
);
|
||||||
|
})()
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -172,3 +172,32 @@ export const parseSRT = (srtContent: string): SubtitleCue[] => {
|
||||||
// Use the new enhanced parser from subtitleParser.ts
|
// Use the new enhanced parser from subtitleParser.ts
|
||||||
return parseSRTEnhanced(srtContent);
|
return parseSRTEnhanced(srtContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if text contains primarily RTL (right-to-left) characters
|
||||||
|
* Checks for Arabic, Hebrew, Persian, Urdu, and other RTL scripts
|
||||||
|
* Returns true if the majority of non-whitespace characters are RTL
|
||||||
|
*/
|
||||||
|
export const detectRTL = (text: string): boolean => {
|
||||||
|
if (!text || text.length === 0) return false;
|
||||||
|
|
||||||
|
// RTL character ranges
|
||||||
|
// Arabic: U+0600–U+06FF
|
||||||
|
// Arabic Supplement: U+0750–U+077F
|
||||||
|
// Arabic Extended-A: U+08A0–U+08FF
|
||||||
|
// Arabic Presentation Forms-A: U+FB50–U+FDFF
|
||||||
|
// Arabic Presentation Forms-B: U+FE70–U+FEFF
|
||||||
|
// Hebrew: U+0590–U+05FF
|
||||||
|
// Persian/Urdu use Arabic script (no separate range)
|
||||||
|
const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/;
|
||||||
|
|
||||||
|
// Remove whitespace and count characters
|
||||||
|
const nonWhitespace = text.replace(/\s/g, '');
|
||||||
|
if (nonWhitespace.length === 0) return false;
|
||||||
|
|
||||||
|
const rtlCount = (nonWhitespace.match(rtlRegex) || []).length;
|
||||||
|
|
||||||
|
// Consider RTL if at least 30% of non-whitespace characters are RTL
|
||||||
|
// This handles mixed-language subtitles (e.g., Arabic with English numbers)
|
||||||
|
return rtlCount / nonWhitespace.length >= 0.3;
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue