mirror of
https://github.com/genuinetools/reg.git
synced 2024-08-20 13:50:23 -04:00
1059 lines
29 KiB
Go
1059 lines
29 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package bidi
|
|
|
|
import "log"
|
|
|
|
// This implementation is a port based on the reference implementation found at:
|
|
// http://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/
|
|
//
|
|
// described in Unicode Bidirectional Algorithm (UAX #9).
|
|
//
|
|
// Input:
|
|
// There are two levels of input to the algorithm, since clients may prefer to
|
|
// supply some information from out-of-band sources rather than relying on the
|
|
// default behavior.
|
|
//
|
|
// - Bidi class array
|
|
// - Bidi class array, with externally supplied base line direction
|
|
//
|
|
// Output:
|
|
// Output is separated into several stages:
|
|
//
|
|
// - levels array over entire paragraph
|
|
// - reordering array over entire paragraph
|
|
// - levels array over line
|
|
// - reordering array over line
|
|
//
|
|
// Note that for conformance to the Unicode Bidirectional Algorithm,
|
|
// implementations are only required to generate correct reordering and
|
|
// character directionality (odd or even levels) over a line. Generating
|
|
// identical level arrays over a line is not required. Bidi explicit format
|
|
// codes (LRE, RLE, LRO, RLO, PDF) and BN can be assigned arbitrary levels and
|
|
// positions as long as the rest of the input is properly reordered.
|
|
//
|
|
// As the algorithm is defined to operate on a single paragraph at a time, this
|
|
// implementation is written to handle single paragraphs. Thus rule P1 is
|
|
// presumed by this implementation-- the data provided to the implementation is
|
|
// assumed to be a single paragraph, and either contains no 'B' codes, or a
|
|
// single 'B' code at the end of the input. 'B' is allowed as input to
|
|
// illustrate how the algorithm assigns it a level.
|
|
//
|
|
// Also note that rules L3 and L4 depend on the rendering engine that uses the
|
|
// result of the bidi algorithm. This implementation assumes that the rendering
|
|
// engine expects combining marks in visual order (e.g. to the left of their
|
|
// base character in RTL runs) and that it adjusts the glyphs used to render
|
|
// mirrored characters that are in RTL runs so that they render appropriately.
|
|
|
|
// level is the embedding level of a character. Even embedding levels indicate
|
|
// left-to-right order and odd levels indicate right-to-left order. The special
|
|
// level of -1 is reserved for undefined order.
|
|
type level int8
|
|
|
|
const implicitLevel level = -1
|
|
|
|
// in returns if x is equal to any of the values in set.
|
|
func (c Class) in(set ...Class) bool {
|
|
for _, s := range set {
|
|
if c == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// A paragraph contains the state of a paragraph.
|
|
type paragraph struct {
|
|
initialTypes []Class
|
|
|
|
// Arrays of properties needed for paired bracket evaluation in N0
|
|
pairTypes []bracketType // paired Bracket types for paragraph
|
|
pairValues []rune // rune for opening bracket or pbOpen and pbClose; 0 for pbNone
|
|
|
|
embeddingLevel level // default: = implicitLevel;
|
|
|
|
// at the paragraph levels
|
|
resultTypes []Class
|
|
resultLevels []level
|
|
|
|
// Index of matching PDI for isolate initiator characters. For other
|
|
// characters, the value of matchingPDI will be set to -1. For isolate
|
|
// initiators with no matching PDI, matchingPDI will be set to the length of
|
|
// the input string.
|
|
matchingPDI []int
|
|
|
|
// Index of matching isolate initiator for PDI characters. For other
|
|
// characters, and for PDIs with no matching isolate initiator, the value of
|
|
// matchingIsolateInitiator will be set to -1.
|
|
matchingIsolateInitiator []int
|
|
}
|
|
|
|
// newParagraph initializes a paragraph. The user needs to supply a few arrays
|
|
// corresponding to the preprocessed text input. The types correspond to the
|
|
// Unicode BiDi classes for each rune. pairTypes indicates the bracket type for
|
|
// each rune. pairValues provides a unique bracket class identifier for each
|
|
// rune (suggested is the rune of the open bracket for opening and matching
|
|
// close brackets, after normalization). The embedding levels are optional, but
|
|
// may be supplied to encode embedding levels of styled text.
|
|
//
|
|
// TODO: return an error.
|
|
func newParagraph(types []Class, pairTypes []bracketType, pairValues []rune, levels level) *paragraph {
|
|
validateTypes(types)
|
|
validatePbTypes(pairTypes)
|
|
validatePbValues(pairValues, pairTypes)
|
|
validateParagraphEmbeddingLevel(levels)
|
|
|
|
p := ¶graph{
|
|
initialTypes: append([]Class(nil), types...),
|
|
embeddingLevel: levels,
|
|
|
|
pairTypes: pairTypes,
|
|
pairValues: pairValues,
|
|
|
|
resultTypes: append([]Class(nil), types...),
|
|
}
|
|
p.run()
|
|
return p
|
|
}
|
|
|
|
func (p *paragraph) Len() int { return len(p.initialTypes) }
|
|
|
|
// The algorithm. Does not include line-based processing (Rules L1, L2).
|
|
// These are applied later in the line-based phase of the algorithm.
|
|
func (p *paragraph) run() {
|
|
p.determineMatchingIsolates()
|
|
|
|
// 1) determining the paragraph level
|
|
// Rule P1 is the requirement for entering this algorithm.
|
|
// Rules P2, P3.
|
|
// If no externally supplied paragraph embedding level, use default.
|
|
if p.embeddingLevel == implicitLevel {
|
|
p.embeddingLevel = p.determineParagraphEmbeddingLevel(0, p.Len())
|
|
}
|
|
|
|
// Initialize result levels to paragraph embedding level.
|
|
p.resultLevels = make([]level, p.Len())
|
|
setLevels(p.resultLevels, p.embeddingLevel)
|
|
|
|
// 2) Explicit levels and directions
|
|
// Rules X1-X8.
|
|
p.determineExplicitEmbeddingLevels()
|
|
|
|
// Rule X9.
|
|
// We do not remove the embeddings, the overrides, the PDFs, and the BNs
|
|
// from the string explicitly. But they are not copied into isolating run
|
|
// sequences when they are created, so they are removed for all
|
|
// practical purposes.
|
|
|
|
// Rule X10.
|
|
// Run remainder of algorithm one isolating run sequence at a time
|
|
for _, seq := range p.determineIsolatingRunSequences() {
|
|
// 3) resolving weak types
|
|
// Rules W1-W7.
|
|
seq.resolveWeakTypes()
|
|
|
|
// 4a) resolving paired brackets
|
|
// Rule N0
|
|
resolvePairedBrackets(seq)
|
|
|
|
// 4b) resolving neutral types
|
|
// Rules N1-N3.
|
|
seq.resolveNeutralTypes()
|
|
|
|
// 5) resolving implicit embedding levels
|
|
// Rules I1, I2.
|
|
seq.resolveImplicitLevels()
|
|
|
|
// Apply the computed levels and types
|
|
seq.applyLevelsAndTypes()
|
|
}
|
|
|
|
// Assign appropriate levels to 'hide' LREs, RLEs, LROs, RLOs, PDFs, and
|
|
// BNs. This is for convenience, so the resulting level array will have
|
|
// a value for every character.
|
|
p.assignLevelsToCharactersRemovedByX9()
|
|
}
|
|
|
|
// determineMatchingIsolates determines the matching PDI for each isolate
|
|
// initiator and vice versa.
|
|
//
|
|
// Definition BD9.
|
|
//
|
|
// At the end of this function:
|
|
//
|
|
// - The member variable matchingPDI is set to point to the index of the
|
|
// matching PDI character for each isolate initiator character. If there is
|
|
// no matching PDI, it is set to the length of the input text. For other
|
|
// characters, it is set to -1.
|
|
// - The member variable matchingIsolateInitiator is set to point to the
|
|
// index of the matching isolate initiator character for each PDI character.
|
|
// If there is no matching isolate initiator, or the character is not a PDI,
|
|
// it is set to -1.
|
|
func (p *paragraph) determineMatchingIsolates() {
|
|
p.matchingPDI = make([]int, p.Len())
|
|
p.matchingIsolateInitiator = make([]int, p.Len())
|
|
|
|
for i := range p.matchingIsolateInitiator {
|
|
p.matchingIsolateInitiator[i] = -1
|
|
}
|
|
|
|
for i := range p.matchingPDI {
|
|
p.matchingPDI[i] = -1
|
|
|
|
if t := p.resultTypes[i]; t.in(LRI, RLI, FSI) {
|
|
depthCounter := 1
|
|
for j := i + 1; j < p.Len(); j++ {
|
|
if u := p.resultTypes[j]; u.in(LRI, RLI, FSI) {
|
|
depthCounter++
|
|
} else if u == PDI {
|
|
if depthCounter--; depthCounter == 0 {
|
|
p.matchingPDI[i] = j
|
|
p.matchingIsolateInitiator[j] = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if p.matchingPDI[i] == -1 {
|
|
p.matchingPDI[i] = p.Len()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// determineParagraphEmbeddingLevel reports the resolved paragraph direction of
|
|
// the substring limited by the given range [start, end).
|
|
//
|
|
// Determines the paragraph level based on rules P2, P3. This is also used
|
|
// in rule X5c to find if an FSI should resolve to LRI or RLI.
|
|
func (p *paragraph) determineParagraphEmbeddingLevel(start, end int) level {
|
|
var strongType Class = unknownClass
|
|
|
|
// Rule P2.
|
|
for i := start; i < end; i++ {
|
|
if t := p.resultTypes[i]; t.in(L, AL, R) {
|
|
strongType = t
|
|
break
|
|
} else if t.in(FSI, LRI, RLI) {
|
|
i = p.matchingPDI[i] // skip over to the matching PDI
|
|
if i > end {
|
|
log.Panic("assert (i <= end)")
|
|
}
|
|
}
|
|
}
|
|
// Rule P3.
|
|
switch strongType {
|
|
case unknownClass: // none found
|
|
// default embedding level when no strong types found is 0.
|
|
return 0
|
|
case L:
|
|
return 0
|
|
default: // AL, R
|
|
return 1
|
|
}
|
|
}
|
|
|
|
const maxDepth = 125
|
|
|
|
// This stack will store the embedding levels and override and isolated
|
|
// statuses
|
|
type directionalStatusStack struct {
|
|
stackCounter int
|
|
embeddingLevelStack [maxDepth + 1]level
|
|
overrideStatusStack [maxDepth + 1]Class
|
|
isolateStatusStack [maxDepth + 1]bool
|
|
}
|
|
|
|
func (s *directionalStatusStack) empty() { s.stackCounter = 0 }
|
|
func (s *directionalStatusStack) pop() { s.stackCounter-- }
|
|
func (s *directionalStatusStack) depth() int { return s.stackCounter }
|
|
|
|
func (s *directionalStatusStack) push(level level, overrideStatus Class, isolateStatus bool) {
|
|
s.embeddingLevelStack[s.stackCounter] = level
|
|
s.overrideStatusStack[s.stackCounter] = overrideStatus
|
|
s.isolateStatusStack[s.stackCounter] = isolateStatus
|
|
s.stackCounter++
|
|
}
|
|
|
|
func (s *directionalStatusStack) lastEmbeddingLevel() level {
|
|
return s.embeddingLevelStack[s.stackCounter-1]
|
|
}
|
|
|
|
func (s *directionalStatusStack) lastDirectionalOverrideStatus() Class {
|
|
return s.overrideStatusStack[s.stackCounter-1]
|
|
}
|
|
|
|
func (s *directionalStatusStack) lastDirectionalIsolateStatus() bool {
|
|
return s.isolateStatusStack[s.stackCounter-1]
|
|
}
|
|
|
|
// Determine explicit levels using rules X1 - X8
|
|
func (p *paragraph) determineExplicitEmbeddingLevels() {
|
|
var stack directionalStatusStack
|
|
var overflowIsolateCount, overflowEmbeddingCount, validIsolateCount int
|
|
|
|
// Rule X1.
|
|
stack.push(p.embeddingLevel, ON, false)
|
|
|
|
for i, t := range p.resultTypes {
|
|
// Rules X2, X3, X4, X5, X5a, X5b, X5c
|
|
switch t {
|
|
case RLE, LRE, RLO, LRO, RLI, LRI, FSI:
|
|
isIsolate := t.in(RLI, LRI, FSI)
|
|
isRTL := t.in(RLE, RLO, RLI)
|
|
|
|
// override if this is an FSI that resolves to RLI
|
|
if t == FSI {
|
|
isRTL = (p.determineParagraphEmbeddingLevel(i+1, p.matchingPDI[i]) == 1)
|
|
}
|
|
if isIsolate {
|
|
p.resultLevels[i] = stack.lastEmbeddingLevel()
|
|
if stack.lastDirectionalOverrideStatus() != ON {
|
|
p.resultTypes[i] = stack.lastDirectionalOverrideStatus()
|
|
}
|
|
}
|
|
|
|
var newLevel level
|
|
if isRTL {
|
|
// least greater odd
|
|
newLevel = (stack.lastEmbeddingLevel() + 1) | 1
|
|
} else {
|
|
// least greater even
|
|
newLevel = (stack.lastEmbeddingLevel() + 2) &^ 1
|
|
}
|
|
|
|
if newLevel <= maxDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0 {
|
|
if isIsolate {
|
|
validIsolateCount++
|
|
}
|
|
// Push new embedding level, override status, and isolated
|
|
// status.
|
|
// No check for valid stack counter, since the level check
|
|
// suffices.
|
|
switch t {
|
|
case LRO:
|
|
stack.push(newLevel, L, isIsolate)
|
|
case RLO:
|
|
stack.push(newLevel, R, isIsolate)
|
|
default:
|
|
stack.push(newLevel, ON, isIsolate)
|
|
}
|
|
// Not really part of the spec
|
|
if !isIsolate {
|
|
p.resultLevels[i] = newLevel
|
|
}
|
|
} else {
|
|
// This is an invalid explicit formatting character,
|
|
// so apply the "Otherwise" part of rules X2-X5b.
|
|
if isIsolate {
|
|
overflowIsolateCount++
|
|
} else { // !isIsolate
|
|
if overflowIsolateCount == 0 {
|
|
overflowEmbeddingCount++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rule X6a
|
|
case PDI:
|
|
if overflowIsolateCount > 0 {
|
|
overflowIsolateCount--
|
|
} else if validIsolateCount == 0 {
|
|
// do nothing
|
|
} else {
|
|
overflowEmbeddingCount = 0
|
|
for !stack.lastDirectionalIsolateStatus() {
|
|
stack.pop()
|
|
}
|
|
stack.pop()
|
|
validIsolateCount--
|
|
}
|
|
p.resultLevels[i] = stack.lastEmbeddingLevel()
|
|
|
|
// Rule X7
|
|
case PDF:
|
|
// Not really part of the spec
|
|
p.resultLevels[i] = stack.lastEmbeddingLevel()
|
|
|
|
if overflowIsolateCount > 0 {
|
|
// do nothing
|
|
} else if overflowEmbeddingCount > 0 {
|
|
overflowEmbeddingCount--
|
|
} else if !stack.lastDirectionalIsolateStatus() && stack.depth() >= 2 {
|
|
stack.pop()
|
|
}
|
|
|
|
case B: // paragraph separator.
|
|
// Rule X8.
|
|
|
|
// These values are reset for clarity, in this implementation B
|
|
// can only occur as the last code in the array.
|
|
stack.empty()
|
|
overflowIsolateCount = 0
|
|
overflowEmbeddingCount = 0
|
|
validIsolateCount = 0
|
|
p.resultLevels[i] = p.embeddingLevel
|
|
|
|
default:
|
|
p.resultLevels[i] = stack.lastEmbeddingLevel()
|
|
if stack.lastDirectionalOverrideStatus() != ON {
|
|
p.resultTypes[i] = stack.lastDirectionalOverrideStatus()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type isolatingRunSequence struct {
|
|
p *paragraph
|
|
|
|
indexes []int // indexes to the original string
|
|
|
|
types []Class // type of each character using the index
|
|
resolvedLevels []level // resolved levels after application of rules
|
|
level level
|
|
sos, eos Class
|
|
}
|
|
|
|
func (i *isolatingRunSequence) Len() int { return len(i.indexes) }
|
|
|
|
func maxLevel(a, b level) level {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Rule X10, second bullet: Determine the start-of-sequence (sos) and end-of-sequence (eos) types,
|
|
// either L or R, for each isolating run sequence.
|
|
func (p *paragraph) isolatingRunSequence(indexes []int) *isolatingRunSequence {
|
|
length := len(indexes)
|
|
types := make([]Class, length)
|
|
for i, x := range indexes {
|
|
types[i] = p.resultTypes[x]
|
|
}
|
|
|
|
// assign level, sos and eos
|
|
prevChar := indexes[0] - 1
|
|
for prevChar >= 0 && isRemovedByX9(p.initialTypes[prevChar]) {
|
|
prevChar--
|
|
}
|
|
prevLevel := p.embeddingLevel
|
|
if prevChar >= 0 {
|
|
prevLevel = p.resultLevels[prevChar]
|
|
}
|
|
|
|
var succLevel level
|
|
lastType := types[length-1]
|
|
if lastType.in(LRI, RLI, FSI) {
|
|
succLevel = p.embeddingLevel
|
|
} else {
|
|
// the first character after the end of run sequence
|
|
limit := indexes[length-1] + 1
|
|
for ; limit < p.Len() && isRemovedByX9(p.initialTypes[limit]); limit++ {
|
|
|
|
}
|
|
succLevel = p.embeddingLevel
|
|
if limit < p.Len() {
|
|
succLevel = p.resultLevels[limit]
|
|
}
|
|
}
|
|
level := p.resultLevels[indexes[0]]
|
|
return &isolatingRunSequence{
|
|
p: p,
|
|
indexes: indexes,
|
|
types: types,
|
|
level: level,
|
|
sos: typeForLevel(maxLevel(prevLevel, level)),
|
|
eos: typeForLevel(maxLevel(succLevel, level)),
|
|
}
|
|
}
|
|
|
|
// Resolving weak types Rules W1-W7.
|
|
//
|
|
// Note that some weak types (EN, AN) remain after this processing is
|
|
// complete.
|
|
func (s *isolatingRunSequence) resolveWeakTypes() {
|
|
|
|
// on entry, only these types remain
|
|
s.assertOnly(L, R, AL, EN, ES, ET, AN, CS, B, S, WS, ON, NSM, LRI, RLI, FSI, PDI)
|
|
|
|
// Rule W1.
|
|
// Changes all NSMs.
|
|
preceedingCharacterType := s.sos
|
|
for i, t := range s.types {
|
|
if t == NSM {
|
|
s.types[i] = preceedingCharacterType
|
|
} else {
|
|
if t.in(LRI, RLI, FSI, PDI) {
|
|
preceedingCharacterType = ON
|
|
}
|
|
preceedingCharacterType = t
|
|
}
|
|
}
|
|
|
|
// Rule W2.
|
|
// EN does not change at the start of the run, because sos != AL.
|
|
for i, t := range s.types {
|
|
if t == EN {
|
|
for j := i - 1; j >= 0; j-- {
|
|
if t := s.types[j]; t.in(L, R, AL) {
|
|
if t == AL {
|
|
s.types[i] = AN
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rule W3.
|
|
for i, t := range s.types {
|
|
if t == AL {
|
|
s.types[i] = R
|
|
}
|
|
}
|
|
|
|
// Rule W4.
|
|
// Since there must be values on both sides for this rule to have an
|
|
// effect, the scan skips the first and last value.
|
|
//
|
|
// Although the scan proceeds left to right, and changes the type
|
|
// values in a way that would appear to affect the computations
|
|
// later in the scan, there is actually no problem. A change in the
|
|
// current value can only affect the value to its immediate right,
|
|
// and only affect it if it is ES or CS. But the current value can
|
|
// only change if the value to its right is not ES or CS. Thus
|
|
// either the current value will not change, or its change will have
|
|
// no effect on the remainder of the analysis.
|
|
|
|
for i := 1; i < s.Len()-1; i++ {
|
|
t := s.types[i]
|
|
if t == ES || t == CS {
|
|
prevSepType := s.types[i-1]
|
|
succSepType := s.types[i+1]
|
|
if prevSepType == EN && succSepType == EN {
|
|
s.types[i] = EN
|
|
} else if s.types[i] == CS && prevSepType == AN && succSepType == AN {
|
|
s.types[i] = AN
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rule W5.
|
|
for i, t := range s.types {
|
|
if t == ET {
|
|
// locate end of sequence
|
|
runStart := i
|
|
runEnd := s.findRunLimit(runStart, ET)
|
|
|
|
// check values at ends of sequence
|
|
t := s.sos
|
|
if runStart > 0 {
|
|
t = s.types[runStart-1]
|
|
}
|
|
if t != EN {
|
|
t = s.eos
|
|
if runEnd < len(s.types) {
|
|
t = s.types[runEnd]
|
|
}
|
|
}
|
|
if t == EN {
|
|
setTypes(s.types[runStart:runEnd], EN)
|
|
}
|
|
// continue at end of sequence
|
|
i = runEnd
|
|
}
|
|
}
|
|
|
|
// Rule W6.
|
|
for i, t := range s.types {
|
|
if t.in(ES, ET, CS) {
|
|
s.types[i] = ON
|
|
}
|
|
}
|
|
|
|
// Rule W7.
|
|
for i, t := range s.types {
|
|
if t == EN {
|
|
// set default if we reach start of run
|
|
prevStrongType := s.sos
|
|
for j := i - 1; j >= 0; j-- {
|
|
t = s.types[j]
|
|
if t == L || t == R { // AL's have been changed to R
|
|
prevStrongType = t
|
|
break
|
|
}
|
|
}
|
|
if prevStrongType == L {
|
|
s.types[i] = L
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 6) resolving neutral types Rules N1-N2.
|
|
func (s *isolatingRunSequence) resolveNeutralTypes() {
|
|
|
|
// on entry, only these types can be in resultTypes
|
|
s.assertOnly(L, R, EN, AN, B, S, WS, ON, RLI, LRI, FSI, PDI)
|
|
|
|
for i, t := range s.types {
|
|
switch t {
|
|
case WS, ON, B, S, RLI, LRI, FSI, PDI:
|
|
// find bounds of run of neutrals
|
|
runStart := i
|
|
runEnd := s.findRunLimit(runStart, B, S, WS, ON, RLI, LRI, FSI, PDI)
|
|
|
|
// determine effective types at ends of run
|
|
var leadType, trailType Class
|
|
|
|
// Note that the character found can only be L, R, AN, or
|
|
// EN.
|
|
if runStart == 0 {
|
|
leadType = s.sos
|
|
} else {
|
|
leadType = s.types[runStart-1]
|
|
if leadType.in(AN, EN) {
|
|
leadType = R
|
|
}
|
|
}
|
|
if runEnd == len(s.types) {
|
|
trailType = s.eos
|
|
} else {
|
|
trailType = s.types[runEnd]
|
|
if trailType.in(AN, EN) {
|
|
trailType = R
|
|
}
|
|
}
|
|
|
|
var resolvedType Class
|
|
if leadType == trailType {
|
|
// Rule N1.
|
|
resolvedType = leadType
|
|
} else {
|
|
// Rule N2.
|
|
// Notice the embedding level of the run is used, not
|
|
// the paragraph embedding level.
|
|
resolvedType = typeForLevel(s.level)
|
|
}
|
|
|
|
setTypes(s.types[runStart:runEnd], resolvedType)
|
|
|
|
// skip over run of (former) neutrals
|
|
i = runEnd
|
|
}
|
|
}
|
|
}
|
|
|
|
func setLevels(levels []level, newLevel level) {
|
|
for i := range levels {
|
|
levels[i] = newLevel
|
|
}
|
|
}
|
|
|
|
func setTypes(types []Class, newType Class) {
|
|
for i := range types {
|
|
types[i] = newType
|
|
}
|
|
}
|
|
|
|
// 7) resolving implicit embedding levels Rules I1, I2.
|
|
func (s *isolatingRunSequence) resolveImplicitLevels() {
|
|
|
|
// on entry, only these types can be in resultTypes
|
|
s.assertOnly(L, R, EN, AN)
|
|
|
|
s.resolvedLevels = make([]level, len(s.types))
|
|
setLevels(s.resolvedLevels, s.level)
|
|
|
|
if (s.level & 1) == 0 { // even level
|
|
for i, t := range s.types {
|
|
// Rule I1.
|
|
if t == L {
|
|
// no change
|
|
} else if t == R {
|
|
s.resolvedLevels[i] += 1
|
|
} else { // t == AN || t == EN
|
|
s.resolvedLevels[i] += 2
|
|
}
|
|
}
|
|
} else { // odd level
|
|
for i, t := range s.types {
|
|
// Rule I2.
|
|
if t == R {
|
|
// no change
|
|
} else { // t == L || t == AN || t == EN
|
|
s.resolvedLevels[i] += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Applies the levels and types resolved in rules W1-I2 to the
|
|
// resultLevels array.
|
|
func (s *isolatingRunSequence) applyLevelsAndTypes() {
|
|
for i, x := range s.indexes {
|
|
s.p.resultTypes[x] = s.types[i]
|
|
s.p.resultLevels[x] = s.resolvedLevels[i]
|
|
}
|
|
}
|
|
|
|
// Return the limit of the run consisting only of the types in validSet
|
|
// starting at index. This checks the value at index, and will return
|
|
// index if that value is not in validSet.
|
|
func (s *isolatingRunSequence) findRunLimit(index int, validSet ...Class) int {
|
|
loop:
|
|
for ; index < len(s.types); index++ {
|
|
t := s.types[index]
|
|
for _, valid := range validSet {
|
|
if t == valid {
|
|
continue loop
|
|
}
|
|
}
|
|
return index // didn't find a match in validSet
|
|
}
|
|
return len(s.types)
|
|
}
|
|
|
|
// Algorithm validation. Assert that all values in types are in the
|
|
// provided set.
|
|
func (s *isolatingRunSequence) assertOnly(codes ...Class) {
|
|
loop:
|
|
for i, t := range s.types {
|
|
for _, c := range codes {
|
|
if t == c {
|
|
continue loop
|
|
}
|
|
}
|
|
log.Panicf("invalid bidi code %v present in assertOnly at position %d", t, s.indexes[i])
|
|
}
|
|
}
|
|
|
|
// determineLevelRuns returns an array of level runs. Each level run is
|
|
// described as an array of indexes into the input string.
|
|
//
|
|
// Determines the level runs. Rule X9 will be applied in determining the
|
|
// runs, in the way that makes sure the characters that are supposed to be
|
|
// removed are not included in the runs.
|
|
func (p *paragraph) determineLevelRuns() [][]int {
|
|
run := []int{}
|
|
allRuns := [][]int{}
|
|
currentLevel := implicitLevel
|
|
|
|
for i := range p.initialTypes {
|
|
if !isRemovedByX9(p.initialTypes[i]) {
|
|
if p.resultLevels[i] != currentLevel {
|
|
// we just encountered a new run; wrap up last run
|
|
if currentLevel >= 0 { // only wrap it up if there was a run
|
|
allRuns = append(allRuns, run)
|
|
run = nil
|
|
}
|
|
// Start new run
|
|
currentLevel = p.resultLevels[i]
|
|
}
|
|
run = append(run, i)
|
|
}
|
|
}
|
|
// Wrap up the final run, if any
|
|
if len(run) > 0 {
|
|
allRuns = append(allRuns, run)
|
|
}
|
|
return allRuns
|
|
}
|
|
|
|
// Definition BD13. Determine isolating run sequences.
|
|
func (p *paragraph) determineIsolatingRunSequences() []*isolatingRunSequence {
|
|
levelRuns := p.determineLevelRuns()
|
|
|
|
// Compute the run that each character belongs to
|
|
runForCharacter := make([]int, p.Len())
|
|
for i, run := range levelRuns {
|
|
for _, index := range run {
|
|
runForCharacter[index] = i
|
|
}
|
|
}
|
|
|
|
sequences := []*isolatingRunSequence{}
|
|
|
|
var currentRunSequence []int
|
|
|
|
for _, run := range levelRuns {
|
|
first := run[0]
|
|
if p.initialTypes[first] != PDI || p.matchingIsolateInitiator[first] == -1 {
|
|
currentRunSequence = nil
|
|
// int run = i;
|
|
for {
|
|
// Copy this level run into currentRunSequence
|
|
currentRunSequence = append(currentRunSequence, run...)
|
|
|
|
last := currentRunSequence[len(currentRunSequence)-1]
|
|
lastT := p.initialTypes[last]
|
|
if lastT.in(LRI, RLI, FSI) && p.matchingPDI[last] != p.Len() {
|
|
run = levelRuns[runForCharacter[p.matchingPDI[last]]]
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
sequences = append(sequences, p.isolatingRunSequence(currentRunSequence))
|
|
}
|
|
}
|
|
return sequences
|
|
}
|
|
|
|
// Assign level information to characters removed by rule X9. This is for
|
|
// ease of relating the level information to the original input data. Note
|
|
// that the levels assigned to these codes are arbitrary, they're chosen so
|
|
// as to avoid breaking level runs.
|
|
func (p *paragraph) assignLevelsToCharactersRemovedByX9() {
|
|
for i, t := range p.initialTypes {
|
|
if t.in(LRE, RLE, LRO, RLO, PDF, BN) {
|
|
p.resultTypes[i] = t
|
|
p.resultLevels[i] = -1
|
|
}
|
|
}
|
|
// now propagate forward the levels information (could have
|
|
// propagated backward, the main thing is not to introduce a level
|
|
// break where one doesn't already exist).
|
|
|
|
if p.resultLevels[0] == -1 {
|
|
p.resultLevels[0] = p.embeddingLevel
|
|
}
|
|
for i := 1; i < len(p.initialTypes); i++ {
|
|
if p.resultLevels[i] == -1 {
|
|
p.resultLevels[i] = p.resultLevels[i-1]
|
|
}
|
|
}
|
|
// Embedding information is for informational purposes only so need not be
|
|
// adjusted.
|
|
}
|
|
|
|
//
|
|
// Output
|
|
//
|
|
|
|
// getLevels computes levels array breaking lines at offsets in linebreaks.
|
|
// Rule L1.
|
|
//
|
|
// The linebreaks array must include at least one value. The values must be
|
|
// in strictly increasing order (no duplicates) between 1 and the length of
|
|
// the text, inclusive. The last value must be the length of the text.
|
|
func (p *paragraph) getLevels(linebreaks []int) []level {
|
|
// Note that since the previous processing has removed all
|
|
// P, S, and WS values from resultTypes, the values referred to
|
|
// in these rules are the initial types, before any processing
|
|
// has been applied (including processing of overrides).
|
|
//
|
|
// This example implementation has reinserted explicit format codes
|
|
// and BN, in order that the levels array correspond to the
|
|
// initial text. Their final placement is not normative.
|
|
// These codes are treated like WS in this implementation,
|
|
// so they don't interrupt sequences of WS.
|
|
|
|
validateLineBreaks(linebreaks, p.Len())
|
|
|
|
result := append([]level(nil), p.resultLevels...)
|
|
|
|
// don't worry about linebreaks since if there is a break within
|
|
// a series of WS values preceding S, the linebreak itself
|
|
// causes the reset.
|
|
for i, t := range p.initialTypes {
|
|
if t.in(B, S) {
|
|
// Rule L1, clauses one and two.
|
|
result[i] = p.embeddingLevel
|
|
|
|
// Rule L1, clause three.
|
|
for j := i - 1; j >= 0; j-- {
|
|
if isWhitespace(p.initialTypes[j]) { // including format codes
|
|
result[j] = p.embeddingLevel
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rule L1, clause four.
|
|
start := 0
|
|
for _, limit := range linebreaks {
|
|
for j := limit - 1; j >= start; j-- {
|
|
if isWhitespace(p.initialTypes[j]) { // including format codes
|
|
result[j] = p.embeddingLevel
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
start = limit
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// getReordering returns the reordering of lines from a visual index to a
|
|
// logical index for line breaks at the given offsets.
|
|
//
|
|
// Lines are concatenated from left to right. So for example, the fifth
|
|
// character from the left on the third line is
|
|
//
|
|
// getReordering(linebreaks)[linebreaks[1] + 4]
|
|
//
|
|
// (linebreaks[1] is the position after the last character of the second
|
|
// line, which is also the index of the first character on the third line,
|
|
// and adding four gets the fifth character from the left).
|
|
//
|
|
// The linebreaks array must include at least one value. The values must be
|
|
// in strictly increasing order (no duplicates) between 1 and the length of
|
|
// the text, inclusive. The last value must be the length of the text.
|
|
func (p *paragraph) getReordering(linebreaks []int) []int {
|
|
validateLineBreaks(linebreaks, p.Len())
|
|
|
|
return computeMultilineReordering(p.getLevels(linebreaks), linebreaks)
|
|
}
|
|
|
|
// Return multiline reordering array for a given level array. Reordering
|
|
// does not occur across a line break.
|
|
func computeMultilineReordering(levels []level, linebreaks []int) []int {
|
|
result := make([]int, len(levels))
|
|
|
|
start := 0
|
|
for _, limit := range linebreaks {
|
|
tempLevels := make([]level, limit-start)
|
|
copy(tempLevels, levels[start:])
|
|
|
|
for j, order := range computeReordering(tempLevels) {
|
|
result[start+j] = order + start
|
|
}
|
|
start = limit
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Return reordering array for a given level array. This reorders a single
|
|
// line. The reordering is a visual to logical map. For example, the
|
|
// leftmost char is string.charAt(order[0]). Rule L2.
|
|
func computeReordering(levels []level) []int {
|
|
result := make([]int, len(levels))
|
|
// initialize order
|
|
for i := range result {
|
|
result[i] = i
|
|
}
|
|
|
|
// locate highest level found on line.
|
|
// Note the rules say text, but no reordering across line bounds is
|
|
// performed, so this is sufficient.
|
|
highestLevel := level(0)
|
|
lowestOddLevel := level(maxDepth + 2)
|
|
for _, level := range levels {
|
|
if level > highestLevel {
|
|
highestLevel = level
|
|
}
|
|
if level&1 != 0 && level < lowestOddLevel {
|
|
lowestOddLevel = level
|
|
}
|
|
}
|
|
|
|
for level := highestLevel; level >= lowestOddLevel; level-- {
|
|
for i := 0; i < len(levels); i++ {
|
|
if levels[i] >= level {
|
|
// find range of text at or above this level
|
|
start := i
|
|
limit := i + 1
|
|
for limit < len(levels) && levels[limit] >= level {
|
|
limit++
|
|
}
|
|
|
|
for j, k := start, limit-1; j < k; j, k = j+1, k-1 {
|
|
result[j], result[k] = result[k], result[j]
|
|
}
|
|
// skip to end of level run
|
|
i = limit
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// isWhitespace reports whether the type is considered a whitespace type for the
|
|
// line break rules.
|
|
func isWhitespace(c Class) bool {
|
|
switch c {
|
|
case LRE, RLE, LRO, RLO, PDF, LRI, RLI, FSI, PDI, BN, WS:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isRemovedByX9 reports whether the type is one of the types removed in X9.
|
|
func isRemovedByX9(c Class) bool {
|
|
switch c {
|
|
case LRE, RLE, LRO, RLO, PDF, BN:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// typeForLevel reports the strong type (L or R) corresponding to the level.
|
|
func typeForLevel(level level) Class {
|
|
if (level & 0x1) == 0 {
|
|
return L
|
|
}
|
|
return R
|
|
}
|
|
|
|
// TODO: change validation to not panic
|
|
|
|
func validateTypes(types []Class) {
|
|
if len(types) == 0 {
|
|
log.Panic("types is null")
|
|
}
|
|
for i, t := range types[:len(types)-1] {
|
|
if t == B {
|
|
log.Panicf("B type before end of paragraph at index: %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func validateParagraphEmbeddingLevel(embeddingLevel level) {
|
|
if embeddingLevel != implicitLevel &&
|
|
embeddingLevel != 0 &&
|
|
embeddingLevel != 1 {
|
|
log.Panicf("illegal paragraph embedding level: %d", embeddingLevel)
|
|
}
|
|
}
|
|
|
|
func validateLineBreaks(linebreaks []int, textLength int) {
|
|
prev := 0
|
|
for i, next := range linebreaks {
|
|
if next <= prev {
|
|
log.Panicf("bad linebreak: %d at index: %d", next, i)
|
|
}
|
|
prev = next
|
|
}
|
|
if prev != textLength {
|
|
log.Panicf("last linebreak was %d, want %d", prev, textLength)
|
|
}
|
|
}
|
|
|
|
func validatePbTypes(pairTypes []bracketType) {
|
|
if len(pairTypes) == 0 {
|
|
log.Panic("pairTypes is null")
|
|
}
|
|
for i, pt := range pairTypes {
|
|
switch pt {
|
|
case bpNone, bpOpen, bpClose:
|
|
default:
|
|
log.Panicf("illegal pairType value at %d: %v", i, pairTypes[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func validatePbValues(pairValues []rune, pairTypes []bracketType) {
|
|
if pairValues == nil {
|
|
log.Panic("pairValues is null")
|
|
}
|
|
if len(pairTypes) != len(pairValues) {
|
|
log.Panic("pairTypes is different length from pairValues")
|
|
}
|
|
}
|