forked from joejulian/gio
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:
+14
-63
@@ -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.
|
||||
func (s *shaperImpl) shapeAndWrapText(params Parameters, txt []rune) (_ []shaping.Line, truncated int) {
|
||||
wc := shaping.WrapConfig{
|
||||
Direction: mapDirection(params.Locale.Direction),
|
||||
TruncateAfterLines: params.MaxLines,
|
||||
TextContinues: params.forceTruncate,
|
||||
BreakPolicy: wrapPolicyToGoText(params.WrapPolicy),
|
||||
@@ -871,8 +872,9 @@ func toLine(faceToIndex map[*font.Font]int, o shaping.Line, dir system.TextDirec
|
||||
return line{}
|
||||
}
|
||||
line := line{
|
||||
runs: make([]runLayout, len(o)),
|
||||
direction: dir,
|
||||
runs: make([]runLayout, len(o)),
|
||||
direction: dir,
|
||||
visualOrder: make([]int, len(o)),
|
||||
}
|
||||
maxSize := fixed.Int26_6(0)
|
||||
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,
|
||||
Offset: line.runeCount,
|
||||
},
|
||||
Direction: unmapDirection(run.Direction),
|
||||
face: run.Face,
|
||||
Advance: run.Advance,
|
||||
PPEM: run.Size,
|
||||
Direction: unmapDirection(run.Direction),
|
||||
face: run.Face,
|
||||
Advance: run.Advance,
|
||||
PPEM: run.Size,
|
||||
VisualPosition: int(run.VisualIndex),
|
||||
}
|
||||
line.visualOrder[run.VisualIndex] = i
|
||||
line.runeCount += run.Runes.Count
|
||||
line.width += run.Advance
|
||||
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
|
||||
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.
|
||||
x := fixed.Int26_6(0)
|
||||
for _, runIdx := range l.visualOrder {
|
||||
l.runs[runIdx].X = x
|
||||
x += l.runs[runIdx].Advance
|
||||
for _, runIdx := range line.visualOrder {
|
||||
line.runs[runIdx].X = x
|
||||
x += line.runs[runIdx].Advance
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package text
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"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) {
|
||||
ltrFace, _ := opentype.Parse(goregular.TTF)
|
||||
rtlFace, _ := opentype.Parse(nsareg.TTF)
|
||||
|
||||
Reference in New Issue
Block a user