| // 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: |
| // https://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. |
| precedingCharacterType := s.sos |
| for i, t := range s.types { |
| if t == NSM { |
| s.types[i] = precedingCharacterType |
| } else { |
| if t.in(LRI, RLI, FSI, PDI) { |
| precedingCharacterType = ON |
| } |
| precedingCharacterType = 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") |
| } |
| } |