diff --git a/src/SixLabors.Fonts/TextLayout.cs b/src/SixLabors.Fonts/TextLayout.cs index ae56132a..976b00ff 100644 --- a/src/SixLabors.Fonts/TextLayout.cs +++ b/src/SixLabors.Fonts/TextLayout.cs @@ -537,7 +537,18 @@ private static IEnumerable 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), @@ -668,7 +679,7 @@ private static IEnumerable LayoutLineVerticalMixed( continue; } - if (data.IsRotated) + if (data.IsTransformed) { int j = 0; foreach (GlyphMetrics metric in data.Metrics) @@ -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. @@ -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++; @@ -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; } @@ -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); @@ -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++) { @@ -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; @@ -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) { @@ -1181,7 +1197,6 @@ VerticalOrientationType.Rotate or } // Find the next line break. - bool lastMandatory = lastLineBreak.Required; if (currentLineBreak.PositionWrap == codePointIndex) { lastLineBreak = currentLineBreak; @@ -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; @@ -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; @@ -1257,7 +1259,7 @@ VerticalOrientationType.Rotate or bidiRuns[bidiMap[codePointIndex]], graphemeIndex, codePointIndex, - isRotated, + shouldRotate || shouldOffset, isDecomposed, stringIndex); } @@ -1323,7 +1325,7 @@ public void Add( BidiRun bidiRun, int graphemeIndex, int offset, - bool isRotated, + bool isTransformed, bool isDecomposed, int stringIndex) { @@ -1344,7 +1346,7 @@ public void Add( bidiRun, graphemeIndex, offset, - isRotated, + isTransformed, isDecomposed, stringIndex)); } @@ -1695,7 +1697,7 @@ public GlyphLayoutData( BidiRun bidiRun, int graphemeIndex, int offset, - bool isRotated, + bool isTransformed, bool isDecomposed, int stringIndex) { @@ -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; } @@ -1735,7 +1737,7 @@ public GlyphLayoutData( public int Offset { get; } - public bool IsRotated { get; } + public bool IsTransformed { get; } public bool IsDecomposed { get; }