diff --git a/Source/SubtitleProviderSsaAss.h b/Source/SubtitleProviderSsaAss.h index fd4996ddff..ab0df8b34d 100644 --- a/Source/SubtitleProviderSsaAss.h +++ b/Source/SubtitleProviderSsaAss.h @@ -202,14 +202,37 @@ class SubtitleProviderSsaAss : public SubtitleProvider if ((marginL > 0 || marginR > 0 || marginV > 0) && width > 0 && height > 0) { - TimedTextPadding padding; + // Multiple caveats here: + // 1. For TimedTextPadding, the XAML renderer is mixing up the directions. + // Opposed to the alignment enums where Before/After means Top/Bottom + // it considers Start/End as Top/Bottom, so we need to accommodate. + // 2. When non-zero margin values occur in a dialogue line, those values + // only override the individual values from the style not all at once, + // so we need individual checks here. + // 3. MarginV is a top margin for top-aligned blocks and a bottom margin for + // bottom-aligned blocks - even though the Word doc tells this for style + // defined margins and otherwise for dialog lines, where it talks about + // bottom only - which is incorrect. + // We set both here and clear the non-applicable one(s) later + auto padding = subRegion.Padding(); padding.Unit = TimedTextUnit::Percentage; - padding.Start = (double)marginL * 100 / width; - padding.End = (double)marginR * 100 / width; - padding.Before = 0; - padding.After = (double)marginV * 100 / height; - subRegion = CopyRegion(cueRegion); + if (marginL > 0) + { + padding.Before = (double)marginL * 100 / width; + } + + if (marginR > 0) + { + padding.After = (double)marginR * 100 / width; + } + + if (marginV > 0) + { + padding.Start = (double)marginV * 100 / height; + padding.End = (double)marginV * 100 / height; + } + subRegion.Padding(padding); } @@ -434,11 +457,6 @@ class SubtitleProviderSsaAss : public SubtitleProvider double x = min(posX / width, 1); double y = min(posY / height, 1); - TimedTextPadding padding - { - 0, 0, 0, 0, TimedTextUnit::Percentage - }; - TimedTextSize extent { 0, 0, TimedTextUnit::Percentage @@ -469,19 +487,69 @@ class SubtitleProviderSsaAss : public SubtitleProvider break; } + // Absolute positioning does not change the width (normally) + // MarginL and MarginR still apply, which we cannot replicate + // in all cases + auto padding1 = subRegion.Padding(); + switch (subStyle.LineAlignment()) { case TimedTextLineAlignment::Start: pos.X = x * 100; extent.Width = (1.0 - x) * 100; + + // Both margins have an effect, but a left margin here doesn't change + // the alignment point, so we add the left margin to the right margin + // and set left to zero. + padding1.After += padding1.Before; + padding1.Before = 0; + + // Now subtract the reduced width from the padding (which is meant to + // be applied to a 100% width box) + padding1.After -= (100 - extent.Width); + if (padding1.After < 0) { + // In this case, we cannot replicate proper ass rendering unfortunately + padding1.After = 0; + } break; case TimedTextLineAlignment::End: + pos.X = 0; extent.Width = x * 100; + // Both margins have an effect, but a right margin here doesn't change + // the alignment point, so we add the right margin to the left margin + // and set it to zero. + padding1.Before += padding1.After; + padding1.After = 0; + + // Now subtract the reduced width from the padding (which is meant to + // be applied to a 100% width box) + padding1.Before -= (100 - extent.Width); + if (padding1.Before < 0) { + // In this case, we cannot replicate proper ass rendering unfortunately + padding1.Before = 0; + } break; case TimedTextLineAlignment::Center: size = min(x, 1 - x); pos.X = (x - size) * 100; extent.Width = (size * 2) * 100; + + // Both margins are effective but differing left and right margins + // do not change the alignment point (center). So we average both + // margins and set them to equal values + { + auto average = (padding1.Before + padding1.After) / 2; + + // Subtract the reduced width from the paddings + average -= (100 - extent.Width) / 2; + if (average < 0) { + // In this case, we cannot replicate proper ass rendering unfortunately + average = 0; + } + + padding1.Before = average; + padding1.After = average; + } break; default: break; @@ -489,7 +557,10 @@ class SubtitleProviderSsaAss : public SubtitleProvider subRegion.Position(pos); subRegion.Extent(extent); - subRegion.Padding(padding); + // Top and bottom margin are out of the game with absolute positioning + padding1.Start = 0; + padding1.End = 0; + subRegion.Padding(padding1); } // strip effect from actual text @@ -566,6 +637,32 @@ class SubtitleProviderSsaAss : public SubtitleProvider auto timedText = StringUtils::WStringToPlatformString(str); if (timedText.size() > 0) { + auto padding = subRegion.Padding(); + + // MarginV is top margin when aligned to the top + // and bottom margin when aligned to the bottom + // it is ignored for vertically centered subtitles + switch (subRegion.DisplayAlignment()) { + case TimedTextDisplayAlignment::Before: + padding.End = 0; + break; + case TimedTextDisplayAlignment::After: + padding.Start = 0; + break; + case TimedTextDisplayAlignment::Center: + padding.Start = 0; + padding.End = 0; + break; + } + + subRegion.Padding(padding); + subRegion.ZIndex(layer); + + auto cachedRegion = GetOrAddCachedRegion(subRegion, layer); + auto region = cachedRegion->Region; + + cue.CueRegion(region); + textLine.Text(timedText); cue.Lines().Append(textLine); return cue; @@ -753,10 +850,10 @@ class SubtitleProviderSsaAss : public SubtitleProvider padding.Unit = TimedTextUnit::Percentage; if (width > 0 && height > 0) { - padding.Start = (double)marginL * 100 / width; - padding.End = (double)marginR * 100 / width; - padding.Before = 0; - padding.After = (double)marginV * 100 / height; + padding.Before = (double)marginL * 100 / width; + padding.After = (double)marginR * 100 / width; + padding.Start = (double)marginV * 100 / height; + padding.End = (double)marginV * 100 / height; } else {