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

Fix vertical alignment of non-vertical glyphs #435

Merged
merged 1 commit into from
Dec 18, 2024
Merged
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
76 changes: 39 additions & 37 deletions src/SixLabors.Fonts/TextLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,18 @@ private static IEnumerable<GlyphLayout> LayoutLineVertical(
{
// Align the glyph horizontally and vertically centering vertically around the baseline.
Vector2 scale = new Vector2(data.PointSize) / metric.ScaleFactor;
Vector2 offset = new(0, (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y);

float alignX = 0;
if (data.IsTransformed)
{
// Calculate the horizontal alignment offset:
// - Normalize lsb to zero
// - Center the glyph horizontally within the max line height.
alignX -= metric.LeftSideBearing * scale.X;
alignX += (scaledMaxLineHeight - (metric.Bounds.Size().X * scale.X)) * .5F;
}

Vector2 offset = new(alignX, (metric.Bounds.Max.Y + metric.TopSideBearing) * scale.Y);

glyphs.Add(new GlyphLayout(
new Glyph(metric, data.PointSize),
Expand Down Expand Up @@ -668,7 +679,7 @@ private static IEnumerable<GlyphLayout> LayoutLineVerticalMixed(
continue;
}

if (data.IsRotated)
if (data.IsTransformed)
{
int j = 0;
foreach (GlyphMetrics metric in data.Metrics)
Expand Down Expand Up @@ -887,6 +898,7 @@ private static TextBox BreakLines(
bool keepAll = options.WordBreaking == WordBreaking.KeepAll;
bool breakWord = options.WordBreaking == WordBreaking.BreakWord;
bool isHorizontalLayout = layoutMode.IsHorizontal();
bool isVerticalLayout = layoutMode.IsVertical();
bool isVerticalMixedLayout = layoutMode.IsVerticalMixed();

// Calculate the position of potential line breaks.
Expand Down Expand Up @@ -955,12 +967,19 @@ private static TextBox BreakLines(
// Determine whether the glyph advance should be calculated using vertical or horizontal metrics
// For vertical mixed layout we will rotate glyphs with the vertical orientation type R or TR
// which do not already have a vertical substitution.
bool isRotated = isVerticalMixedLayout &&
bool shouldRotate = isVerticalMixedLayout &&
!isVerticalSubstitution &&
CodePoint.GetVerticalOrientationType(codePoint) is
VerticalOrientationType.Rotate or
VerticalOrientationType.TransformRotate;

// Determine whether the glyph advance should be offset for vertical layout.
bool shouldOffset = isVerticalLayout &&
!isVerticalSubstitution &&
CodePoint.GetVerticalOrientationType(codePoint) is
VerticalOrientationType.Rotate or
VerticalOrientationType.TransformRotate;

if (CodePoint.IsVariationSelector(codePoint))
{
codePointIndex++;
Expand All @@ -977,7 +996,7 @@ VerticalOrientationType.Rotate or
? new float[metrics.Count]
: decomposedAdvancesBuffer[..(isDecomposed ? metrics.Count : 1)];

if (isHorizontalLayout || isRotated)
if (isHorizontalLayout || shouldRotate)
{
glyphAdvance = glyph.AdvanceWidth;
}
Expand Down Expand Up @@ -1005,7 +1024,7 @@ VerticalOrientationType.Rotate or
layoutMode,
options.ColorFontSupport)[0];

if (isHorizontalLayout || isRotated)
if (isHorizontalLayout || shouldRotate)
{
glyphAdvance = spaceMetrics.AdvanceWidth * options.TabWidth;
glyph.SetAdvanceWidth((ushort)glyphAdvance);
Expand All @@ -1031,7 +1050,7 @@ VerticalOrientationType.Rotate or
{
// Standard text.
// If decomposed we need to add the advance; otherwise, use the largest advance for the metrics.
if (isHorizontalLayout || isRotated)
if (isHorizontalLayout || shouldRotate)
{
for (int i = 1; i < metrics.Count; i++)
{
Expand Down Expand Up @@ -1066,7 +1085,7 @@ VerticalOrientationType.Rotate or
}

// Now scale the advance.
if (isHorizontalLayout || isRotated)
if (isHorizontalLayout || shouldRotate)
{
float scaleAX = pointSize / glyph.ScaleFactor.X;
glyphAdvance *= scaleAX;
Expand All @@ -1093,14 +1112,11 @@ VerticalOrientationType.Rotate or
// Mandatory wrap at index.
if (currentLineBreak.PositionWrap == codePointIndex && currentLineBreak.Required)
{
if (textLine.Count > 0)
{
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
textLine = new();
lineAdvance = 0;
requiredBreak = true;
}
textLines.Add(textLine.Finalize());
glyphCount += textLine.Count;
textLine = new();
lineAdvance = 0;
requiredBreak = true;
}
else if (shouldWrap && lineAdvance + glyphAdvance >= wrappingLength)
{
Expand Down Expand Up @@ -1181,7 +1197,6 @@ VerticalOrientationType.Rotate or
}

// Find the next line break.
bool lastMandatory = lastLineBreak.Required;
if (currentLineBreak.PositionWrap == codePointIndex)
{
lastLineBreak = currentLineBreak;
Expand All @@ -1203,22 +1218,9 @@ VerticalOrientationType.Rotate or
continue;
}

// The previous line ended with a non-mandatory break at the wrapping length but the new line starts
// with a mandatory line break. We should not add a new line in this case as the line break has
// already been synthesized.
if (textLine.Count == 0
&& textLines.Count > 0
&& !lastMandatory
&& CodePoint.IsNewLine(codePoint))
{
codePointIndex++;
graphemeCodePointIndex++;
continue;
}

// Do not add new lines unless at position zero.
if (textLine.Count > 0 && CodePoint.IsNewLine(codePoint))
{
// Do not add new lines unless at position zero.
codePointIndex++;
graphemeCodePointIndex++;
continue;
Expand All @@ -1232,7 +1234,7 @@ VerticalOrientationType.Rotate or
// Work out the scaled metrics for the glyph.
GlyphMetrics metric = metrics[i];
float scaleY = pointSize / metric.ScaleFactor.Y;
IMetricsHeader metricsHeader = isHorizontalLayout || isRotated
IMetricsHeader metricsHeader = isHorizontalLayout || shouldRotate
? metric.FontMetrics.HorizontalMetrics
: metric.FontMetrics.VerticalMetrics;
float ascender = metricsHeader.Ascender * scaleY;
Expand All @@ -1257,7 +1259,7 @@ VerticalOrientationType.Rotate or
bidiRuns[bidiMap[codePointIndex]],
graphemeIndex,
codePointIndex,
isRotated,
shouldRotate || shouldOffset,
isDecomposed,
stringIndex);
}
Expand Down Expand Up @@ -1323,7 +1325,7 @@ public void Add(
BidiRun bidiRun,
int graphemeIndex,
int offset,
bool isRotated,
bool isTransformed,
bool isDecomposed,
int stringIndex)
{
Expand All @@ -1344,7 +1346,7 @@ public void Add(
bidiRun,
graphemeIndex,
offset,
isRotated,
isTransformed,
isDecomposed,
stringIndex));
}
Expand Down Expand Up @@ -1695,7 +1697,7 @@ public GlyphLayoutData(
BidiRun bidiRun,
int graphemeIndex,
int offset,
bool isRotated,
bool isTransformed,
bool isDecomposed,
int stringIndex)
{
Expand All @@ -1708,7 +1710,7 @@ public GlyphLayoutData(
this.BidiRun = bidiRun;
this.GraphemeIndex = graphemeIndex;
this.Offset = offset;
this.IsRotated = isRotated;
this.IsTransformed = isTransformed;
this.IsDecomposed = isDecomposed;
this.StringIndex = stringIndex;
}
Expand All @@ -1735,7 +1737,7 @@ public GlyphLayoutData(

public int Offset { get; }

public bool IsRotated { get; }
public bool IsTransformed { get; }

public bool IsDecomposed { get; }

Expand Down
Loading