/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.complexscripts.bidi;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import org.apache.fop.area.inline.Anchor;
import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.area.inline.InlineBlockParent;
import org.apache.fop.area.inline.InlineParent;
import org.apache.fop.area.inline.InlineViewport;
import org.apache.fop.area.inline.Leader;
import org.apache.fop.area.inline.Space;
import org.apache.fop.area.inline.SpaceArea;
import org.apache.fop.area.inline.UnresolvedPageNumber;
import org.apache.fop.area.inline.WordArea;
import org.apache.fop.util.CharUtilities;
// CSOFF: EmptyForIteratorPadCheck
// CSOFF: InnerAssignmentCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: SimplifyBooleanReturnCheck
/**
* The InlineRun
class is a utility class, the instances of which are used
* to capture a sequence of reordering levels associated with an inline area.
*
*
This work was originally authored by Glenn Adams (gadams@apache.org).
*/ public class InlineRun { private InlineArea inline; private int[] levels; private int minLevel; private int maxLevel; private int reversals; /** * Primary constructor. * @param inline which generated this inline run * @param levels levels array */ public InlineRun ( InlineArea inline, int[] levels ) { assert inline != null; assert levels != null; this.inline = inline; this.levels = levels; setMinMax ( levels ); } /** * Alternate constructor. * @param inline which generated this inline run * @param level for each index * @param count of indices */ public InlineRun ( InlineArea inline, int level, int count ) { this ( inline, makeLevels ( level, count ) ); } /** * Obtain inline area that generated this inline run. * @return inline area that generated this inline run. */ public InlineArea getInline() { return inline; } /** * Obtain minimum bidi level for this run. * @return minimum bidi level */ public int getMinLevel() { return minLevel; } /** * Obtain maximum bidi level for this run. * @return maximum bidi level */ public int getMaxLevel() { return maxLevel; } private void setMinMax ( int[] levels ) { int mn = Integer.MAX_VALUE; int mx = Integer.MIN_VALUE; if ( ( levels != null ) && ( levels.length > 0 ) ) { for ( int i = 0, n = levels.length; i < n; i++ ) { int l = levels [ i ]; if ( l < mn ) { mn = l; } if ( l > mx ) { mx = l; } } } else { mn = mx = -1; } this.minLevel = mn; this.maxLevel = mx; } /** * Determine if this run has homogenous (same valued) bidi levels. * @return true if homogenous */ public boolean isHomogenous() { return minLevel == maxLevel; } /** * Split this inline run into homogenous runs. * @return list of new runs */ public List split() { List runs = new Vector(); for ( int i = 0, n = levels.length; i < n; ) { int l = levels [ i ]; int s = i; int e = s; while ( e < n ) { if ( levels [ e ] != l ) { break; } else { e++; } } if ( s < e ) { runs.add ( new InlineRun ( inline, l, e - s ) ); } i = e; } assert runs.size() < 2 : "heterogeneous inlines not yet supported!!"; return runs; } /** * Update a min/max array to correspond with this run's min/max values. * @param mm reference to min/max array */ public void updateMinMax ( int[] mm ) { if ( minLevel < mm[0] ) { mm[0] = minLevel; } if ( maxLevel > mm[1] ) { mm[1] = maxLevel; } } /** * Determine if run needs mirroring. * @return true if run is homogenous and odd (i.e., right to left) */ public boolean maybeNeedsMirroring() { return ( minLevel == maxLevel ) && ( ( minLevel & 1 ) != 0 ); } /** * Reverse run (by incrementing reversal count, not actually reversing). */ public void reverse() { reversals++; } /** * Reverse inline area if it is a word area and it requires * reversal. * @param mirror if true then also mirror characters */ public void maybeReverseWord ( boolean mirror ) { if ( inline instanceof WordArea ) { WordArea w = (WordArea) inline; // if not already reversed, then reverse now if ( ! w.isReversed() ) { if ( ( reversals & 1 ) != 0 ) { w.reverse ( mirror ); } else if ( mirror && maybeNeedsMirroring() ) { w.mirror(); } } } } @Override public boolean equals ( Object o ) { if ( o instanceof InlineRun ) { InlineRun ir = (InlineRun) o; if ( ir.inline != inline ) { return false; } else if ( ir.minLevel != minLevel ) { return false; } else if ( ir.maxLevel != maxLevel ) { return false; } else if ( ( ir.levels != null ) && ( levels != null ) ) { if ( ir.levels.length != levels.length ) { return false; } else { for ( int i = 0, n = levels.length; i < n; i++ ) { if ( ir.levels[i] != levels[i] ) { return false; } } return true; } } else if ( ( ir.levels == null ) && ( levels == null ) ) { return true; } else { return false; } } else { return false; } } @Override public int hashCode() { int l = ( inline != null ) ? inline.hashCode() : 0; l = ( l ^ minLevel ) + ( l << 19 ); l = ( l ^ maxLevel ) + ( l << 11 ); return l; } @Override public String toString() { StringBuffer sb = new StringBuffer( "RR: { type = \'" ); char c; String content = null; if ( inline instanceof WordArea ) { c = 'W'; content = ( (WordArea) inline ) .getWord(); } else if ( inline instanceof SpaceArea ) { c = 'S'; content = ( (SpaceArea) inline ) .getSpace(); } else if ( inline instanceof Anchor ) { c = 'A'; } else if ( inline instanceof Leader ) { c = 'L'; } else if ( inline instanceof Space ) { c = 'S'; } else if ( inline instanceof UnresolvedPageNumber ) { c = '#'; content = ( (UnresolvedPageNumber) inline ) .getText(); } else if ( inline instanceof InlineBlockParent ) { c = 'B'; } else if ( inline instanceof InlineViewport ) { c = 'V'; } else if ( inline instanceof InlineParent ) { c = 'I'; } else { c = '?'; } sb.append ( c ); sb.append ( "\', levels = \'" ); sb.append ( generateLevels ( levels ) ); sb.append ( "\', min = " ); sb.append ( minLevel ); sb.append ( ", max = " ); sb.append ( maxLevel ); sb.append ( ", reversals = " ); sb.append ( reversals ); sb.append ( ", content = <" ); sb.append ( CharUtilities.toNCRefs ( content ) ); sb.append ( "> }" ); return sb.toString(); } private String generateLevels ( int[] levels ) { StringBuffer lb = new StringBuffer(); int maxLevel = -1; int numLevels = levels.length; for ( int i = 0; i < numLevels; i++ ) { int l = levels [ i ]; if ( l > maxLevel ) { maxLevel = l; } } if ( maxLevel < 0 ) { // leave level buffer empty } else if ( maxLevel < 10 ) { // use string of decimal digits for ( int i = 0; i < numLevels; i++ ) { lb.append ( (char) ( '0' + levels [ i ] ) ); } } else { // use comma separated list boolean first = true; for ( int i = 0; i < numLevels; i++ ) { if ( first ) { first = false; } else { lb.append(','); } lb.append ( levels [ i ] ); } } return lb.toString(); } private static int[] makeLevels ( int level, int count ) { int[] levels = new int [ count ]; Arrays.fill ( levels, level ); return levels; } }