Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASS Fixes 4: Properly handle Margins, adjust for Padding Bug and manage Margins for Abs Positioning #404

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 113 additions & 16 deletions Source/SubtitleProviderSsaAss.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -469,27 +487,80 @@ 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not really understand why the right margin needs to be reduced here (and below). But I guess you tested this with actual files, comparing with how Aegisub renders them, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did. You can easily do for yourself like this:

In Aegisub, open any ASS file and then create a dummy video for it.
Detach the video window (just for ease).
Double-Click an event.
Then you get there:

image

By double-clicking, you can set the position.
By adding {\anX}, you can change the alignment.
And now you can play with different margins:

image

You'll see that Aegisub always calculates with a width of 100% and then it applies the L/R margins, but when left or right aligned, it adds both margins to the opposite side.

Since we cannot have a width of 100% but need to reduce it so the region fits into the video frame, we need to subtract this reduced width from the margins (because it's like as if we had already applied this reduced width as a margin).

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;
}
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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down