Skip to content

Commit

Permalink
[shaping] let the user specify an Input in Split
Browse files Browse the repository at this point in the history
  • Loading branch information
benoitkugler committed Nov 28, 2023
1 parent 107ca8a commit 2f2ea8d
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 32 deletions.
40 changes: 18 additions & 22 deletions shaping/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,25 +127,27 @@ type delimEntry struct {
script language.Script // resolved from the context
}

// Split segments the given [text] according to :
// Split segments the given pre-configured input according to:
// - text direction
// - script
// - face, as defined by [faces]
//
// As a consequence, it sets the following fields of the returned runs :
// Only the input runes in the range [text.RunStart] to [text.RunEnd] will be split.
//
// As a consequence, it sets the following fields of the returned runs:
// - Text, RunStart, RunEnd
// - Direction
// - Script
// - Face
//
// [defaultDirection] is used during bidi ordering, and should refer to the general
// [text.Direction] is used during bidi ordering, and should refer to the general
// context [text] is used in (typically the user system preference for GUI apps.)
//
// The returned sliced is owned by the [Segmenter] and is only valid until
// the next call to [Split].
func (seg *Segmenter) Split(text []rune, faces Fontmap, defaultDirection di.Direction) []Input {
func (seg *Segmenter) Split(text Input, faces Fontmap) []Input {
seg.reset()
seg.splitByBidi(text, defaultDirection)
seg.splitByBidi(text)
seg.splitByScript()
seg.splitByFace(faces)
return seg.buffer1
Expand All @@ -170,44 +172,38 @@ func (seg *Segmenter) reset() {
}

// fills buffer1
func (seg *Segmenter) splitByBidi(text []rune, defaultDirection di.Direction) {
if defaultDirection.Axis() != di.Horizontal || len(text) == 0 {
seg.buffer1 = append(seg.buffer1, Input{
Text: text,
RunStart: 0,
RunEnd: len(text),
Direction: defaultDirection,
})
func (seg *Segmenter) splitByBidi(text Input) {
if text.Direction.Axis() != di.Horizontal || text.RunStart >= text.RunEnd {
seg.buffer1 = append(seg.buffer1, text)
return
}
def := bidi.LeftToRight
if defaultDirection.Progression() == di.TowardTopLeft {
if text.Direction.Progression() == di.TowardTopLeft {
def = bidi.RightToLeft
}
seg.bidiParagraph.SetString(string(text), bidi.DefaultDirection(def))
seg.bidiParagraph.SetString(string(text.Text[text.RunStart:text.RunEnd]), bidi.DefaultDirection(def))
out, err := seg.bidiParagraph.Order()
if err != nil {
seg.buffer1 = append(seg.buffer1, Input{
Text: text,
RunStart: 0,
RunEnd: len(text),
Direction: defaultDirection,
})
seg.buffer1 = append(seg.buffer1, text)
return
}

input := Input{Text: text} // start a rune 0
input := text // start a rune 0 of the run
for i := 0; i < out.NumRuns(); i++ {
currentInput := input
run := out.Run(i)
dir := run.Direction()
_, endRune := run.Pos()
endRune += text.RunStart // shift by the input run position
currentInput.RunEnd = endRune + 1

// override the direction
if dir == bidi.RightToLeft {
currentInput.Direction = di.DirectionRTL
} else {
currentInput.Direction = di.DirectionLTR
}

seg.buffer1 = append(seg.buffer1, currentInput)
input.RunStart = currentInput.RunEnd
}
Expand Down
60 changes: 50 additions & 10 deletions shaping/input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,23 +248,35 @@ func TestSplitBidi(t *testing.T) {
dir di.Direction
}
for _, test := range []struct {
text []rune
expectedRuns []run
text []rune
defaultDirection di.Direction
expectedRuns []run
}{
{
text: ltrSource,
text: ltrSource,
defaultDirection: di.DirectionLTR,
expectedRuns: []run{
{0, len(ltrSource), di.DirectionLTR},
},
},
{
text: rtlSource,
text: ltrSource,
defaultDirection: di.DirectionRTL,
expectedRuns: []run{
{0, len(ltrSource) - 1, di.DirectionLTR},
{len(ltrSource) - 1, len(ltrSource), di.DirectionRTL},
},
},
{
text: rtlSource,
defaultDirection: di.DirectionRTL,
expectedRuns: []run{
{0, len(rtlSource), di.DirectionRTL},
},
},
{
text: bidiSource,
text: bidiSource,
defaultDirection: di.DirectionLTR,
expectedRuns: []run{
// spaces are assigned to LTR runs
{0, 10, di.DirectionLTR},
Expand All @@ -275,7 +287,8 @@ func TestSplitBidi(t *testing.T) {
},
},
{
text: bidi2Source,
text: bidi2Source,
defaultDirection: di.DirectionLTR,
// spaces are assigned to RTL runs
expectedRuns: []run{
{0, 10, di.DirectionRTL},
Expand All @@ -287,9 +300,9 @@ func TestSplitBidi(t *testing.T) {
},
} {
var seg Segmenter
seg.splitByBidi(test.text, di.DirectionLTR)
seg.splitByBidi(Input{Text: test.text, RunEnd: len(test.text), Direction: test.defaultDirection})
inputs := seg.buffer1
tu.Assert(t, len(inputs) == len(test.expectedRuns))
tu.AssertC(t, len(inputs) == len(test.expectedRuns), string(test.text))
for i, run := range test.expectedRuns {
got := inputs[i]
tu.Assert(t, got.RunStart == run.start)
Expand Down Expand Up @@ -347,7 +360,7 @@ func TestSplitScript(t *testing.T) {
}},
} {
var seg Segmenter
seg.splitByBidi(test.text, di.DirectionLTR) // fills buffer1
seg.splitByBidi(Input{Text: test.text, RunEnd: len(test.text), Direction: di.DirectionLTR}) // fills buffer1
tu.Assert(t, len(seg.buffer1) == 1)

seg.splitByScript()
Expand Down Expand Up @@ -420,7 +433,14 @@ func TestSplit(t *testing.T) {
},
},
} {
inputs := seg.Split([]rune(test.text), fm, di.DirectionLTR)
inputs := seg.Split(Input{
Text: []rune(test.text),
RunEnd: len([]rune(test.text)),
Direction: di.DirectionLTR,

Size: 10,
Language: "fr",
}, fm)
tu.Assert(t, len(inputs) == len(test.expectedRuns))
for i, run := range test.expectedRuns {
got := inputs[i]
Expand All @@ -429,6 +449,26 @@ func TestSplit(t *testing.T) {
tu.Assert(t, got.Direction == run.dir)
tu.Assert(t, got.Script == run.script)
tu.Assert(t, got.Face == run.face)
// check that input properties are properly copied
tu.Assert(t, got.Size == 10)
tu.Assert(t, got.Language == "fr")
}

// check that spliting a "middle" text slice is supported
inputs = seg.Split(Input{
Text: []rune("DUMMY" + test.text + "DUMMY"),
RunStart: 5,
RunEnd: 5 + len([]rune(test.text)),
Direction: di.DirectionLTR,
}, fm)
tu.Assert(t, len(inputs) == len(test.expectedRuns))
for i, run := range test.expectedRuns {
got := inputs[i]
tu.Assert(t, got.RunStart == 5+run.start)
tu.Assert(t, got.RunEnd == 5+run.end)
tu.Assert(t, got.Direction == run.dir)
tu.Assert(t, got.Script == run.script)
tu.Assert(t, got.Face == run.face)
}
}
}

0 comments on commit 2f2ea8d

Please sign in to comment.