text: use upstream bidi visual order algorithm

We've migrated the processing of bidi run ordering into the upstream typesetting
package, so now we can just consume the already-ordered runs instead of computing
their ordering ourselves.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2024-12-17 09:26:29 -05:00
parent 0cbbacc45a
commit 26cddb00b1
2 changed files with 14 additions and 178 deletions
+14 -63
View File
@@ -490,6 +490,7 @@ func wrapPolicyToGoText(p WrapPolicy) shaping.LineBreakPolicy {
// shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format. // shapeAndWrapText invokes the text shaper and returns wrapped lines in the shaper's native format.
func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) { func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
wc := shaping.WrapConfig{ wc := shaping.WrapConfig{
Direction: mapDirection(params.Locale.Direction),
TruncateAfterLines: params.MaxLines, TruncateAfterLines: params.MaxLines,
TextContinues: params.forceTruncate, TextContinues: params.forceTruncate,
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy), BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
@@ -871,8 +872,9 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
return line{} return line{}
} }
line := line{ line := line{
runs: make([]runLayout, len(o)), runs: make([]runLayout, len(o)),
direction: dir, direction: dir,
visualOrder: make([]int, len(o)),
} }
maxSize := fixed.Int26_6(0) maxSize := fixed.Int26_6(0)
for i := range o { for i := range o {
@@ -890,11 +892,13 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
Count: run.Runes.Count, Count: run.Runes.Count,
Offset: line.runeCount, Offset: line.runeCount,
}, },
Direction: unmapDirection(run.Direction), Direction: unmapDirection(run.Direction),
face: run.Face, face: run.Face,
Advance: run.Advance, Advance: run.Advance,
PPEM: run.Size, PPEM: run.Size,
VisualPosition: int(run.VisualIndex),
} }
line.visualOrder[run.VisualIndex] = i
line.runeCount += run.Runes.Count line.runeCount += run.Runes.Count
line.width += run.Advance line.width += run.Advance
if line.ascent < run.LineBounds.Ascent { if line.ascent < run.LineBounds.Ascent {
@@ -905,64 +909,11 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
} }
} }
line.lineHeight = maxSize line.lineHeight = maxSize
computeVisualOrder(&line)
return line
}
// computeVisualOrder will populate the Line's VisualOrder field and the
// VisualPosition field of each element in Runs.
func computeVisualOrder(l *line) {
l.visualOrder = make([]int, len(l.runs))
const none = -1
bidiRangeStart := none
// visPos returns the visual position for an individual logically-indexed
// run in this line, taking only the line's overall text direction into
// account.
visPos := func(logicalIndex int) int {
if l.direction.Progression() == system.TowardOrigin {
return len(l.runs) - 1 - logicalIndex
}
return logicalIndex
}
// resolveBidi populated the line's VisualOrder fields for the elements in the
// half-open range [bidiRangeStart:bidiRangeEnd) indicating that those elements
// should be displayed in reverse-visual order.
resolveBidi := func(bidiRangeStart, bidiRangeEnd int) {
firstVisual := bidiRangeEnd - 1
// Just found the end of a bidi range.
for startIdx := bidiRangeStart; startIdx < bidiRangeEnd; startIdx++ {
pos := visPos(firstVisual)
l.runs[startIdx].VisualPosition = pos
l.visualOrder[pos] = startIdx
firstVisual--
}
bidiRangeStart = none
}
for runIdx, run := range l.runs {
if run.Direction.Progression() != l.direction.Progression() {
if bidiRangeStart == none {
bidiRangeStart = runIdx
}
continue
} else if bidiRangeStart != none {
// Just found the end of a bidi range.
resolveBidi(bidiRangeStart, runIdx)
bidiRangeStart = none
}
pos := visPos(runIdx)
l.runs[runIdx].VisualPosition = pos
l.visualOrder[pos] = runIdx
}
if bidiRangeStart != none {
// We ended iteration within a bidi segment, resolve it.
resolveBidi(bidiRangeStart, len(l.runs))
}
// Iterate and resolve the X of each run. // Iterate and resolve the X of each run.
x := fixed.Int26_6(0) x := fixed.Int26_6(0)
for _, runIdx := range l.visualOrder { for _, runIdx := range line.visualOrder {
l.runs[runIdx].X = x line.runs[runIdx].X = x
x += l.runs[runIdx].Advance x += line.runs[runIdx].Advance
} }
return line
} }
-115
View File
@@ -3,7 +3,6 @@ package text
import ( import (
"fmt" "fmt"
"math" "math"
"reflect"
"strconv" "strconv"
"testing" "testing"
@@ -450,120 +449,6 @@ func TestToLine(t *testing.T) {
} }
} }
func TestComputeVisualOrder(t *testing.T) {
type testcase struct {
name string
input line
expectedVisualOrder []int
}
for _, tc := range []testcase{
{
name: "ltr",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{0, 1, 2},
},
{
name: "rtl",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{2, 1, 0},
},
{
name: "bidi-ltr",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{0, 3, 2, 1, 4},
},
{
name: "bidi-ltr-complex",
input: line{
direction: system.LTR,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{1, 0, 2, 4, 3, 5, 7, 6, 8, 10, 9},
},
{
name: "bidi-rtl",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
},
},
expectedVisualOrder: []int{4, 1, 2, 3, 0},
},
{
name: "bidi-rtl-complex",
input: line{
direction: system.RTL,
runs: []runLayout{
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
{Direction: system.RTL},
{Direction: system.LTR},
{Direction: system.LTR},
},
},
expectedVisualOrder: []int{9, 10, 8, 6, 7, 5, 3, 4, 2, 0, 1},
},
} {
t.Run(tc.name, func(t *testing.T) {
computeVisualOrder(&tc.input)
if !reflect.DeepEqual(tc.input.visualOrder, tc.expectedVisualOrder) {
t.Errorf("expected visual order %v, got %v", tc.expectedVisualOrder, tc.input.visualOrder)
}
for i, visualIndex := range tc.input.visualOrder {
if pos := tc.input.runs[visualIndex].VisualPosition; pos != i {
t.Errorf("line.VisualOrder[%d]=%d, but line.Runs[%d].VisualPosition=%d", i, visualIndex, visualIndex, pos)
}
}
})
}
}
func FuzzLayout(f *testing.F) { func FuzzLayout(f *testing.F) {
ltrFace, _ := opentype.Parse(goregular.TTF) ltrFace, _ := opentype.Parse(goregular.TTF)
rtlFace, _ := opentype.Parse(nsareg.TTF) rtlFace, _ := opentype.Parse(nsareg.TTF)