/*
 * 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.List;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.fo.CharIterator;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.traits.Direction;
import org.apache.fop.traits.WritingModeTraits;
import org.apache.fop.traits.WritingModeTraitsGetter;
import org.apache.fop.util.CharUtilities;

// CSOFF: LineLengthCheck

The DelimitedTextRange class implements the "delimited text range" as described by XML-FO 1.1 §5.8, which contains a flattened sequence of characters. Any FO that generates block areas serves as a delimiter.

This work was originally authored by Glenn Adams (gadams@apache.org).

/** * The <code>DelimitedTextRange</code> class implements the "delimited text range" as described * by XML-FO 1.1 §5.8, which contains a flattened sequence of characters. Any FO that generates * block areas serves as a delimiter. * * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> */
public class DelimitedTextRange { private FONode fn; // node that generates this text range private StringBuffer buffer; // flattened character sequence of generating FO nodes private List intervals; // list of intervals over buffer of generating FO nodes
Primary constructor.
Params:
  • fn – node that generates this text range
/** * Primary constructor. * @param fn node that generates this text range */
public DelimitedTextRange(FONode fn) { this.fn = fn; this.buffer = new StringBuffer(); this.intervals = new Vector(); }
Obtain node that generated this text range.
Returns:node that generated this text range
/** * Obtain node that generated this text range. * @return node that generated this text range */
public FONode getNode() { return fn; }
Append interval using characters from character iterator IT.
Params:
  • it – character iterator
  • fn – node that generates interval being appended
/** * Append interval using characters from character iterator IT. * @param it character iterator * @param fn node that generates interval being appended */
public void append(CharIterator it, FONode fn) { if (it != null) { int s = buffer.length(); int e = s; while (it.hasNext()) { char c = it.nextChar(); buffer.append(c); e++; } intervals.add(new TextInterval(fn, s, e)); } }
Append interval using character C.
Params:
  • c – character
  • fn – node that generates interval being appended
/** * Append interval using character C. * @param c character * @param fn node that generates interval being appended */
public void append(char c, FONode fn) { if (c != 0) { int s = buffer.length(); int e = s + 1; buffer.append(c); intervals.add(new TextInterval(fn, s, e)); } }
Determine if range is empty.
Returns:true if range is empty
/** * Determine if range is empty. * @return true if range is empty */
public boolean isEmpty() { return buffer.length() == 0; }
Resolve bidirectional levels for this range.
/** * Resolve bidirectional levels for this range. */
public void resolve() { WritingModeTraitsGetter tg; if ((tg = WritingModeTraits.getWritingModeTraitsGetter(getNode())) != null) { resolve(tg.getInlineProgressionDirection()); } } @Override public String toString() { StringBuffer sb = new StringBuffer("DR: " + fn.getLocalName() + " { <" + CharUtilities.toNCRefs(buffer.toString()) + ">"); sb.append(", intervals <"); boolean first = true; for (Object interval : intervals) { TextInterval ti = (TextInterval) interval; if (first) { first = false; } else { sb.append(','); } sb.append(ti.toString()); } sb.append("> }"); return sb.toString(); } private void resolve(Direction paragraphEmbeddingLevel) { int [] levels; if ((levels = UnicodeBidiAlgorithm.resolveLevels(buffer, paragraphEmbeddingLevel)) != null) { assignLevels(levels); assignBlockLevel(paragraphEmbeddingLevel); assignTextLevels(); } }

Assign resolved levels to all text intervals of this delimited text range.

Has a possible side effect of replacing the intervals array with a new array containing new text intervals, such that each text interval is associated with a single level run.

Params:
  • levels – array of levels each corresponding to each index of the delimited text range
/** * <p>Assign resolved levels to all text intervals of this delimited text range.</p> * <p>Has a possible side effect of replacing the intervals array with a new array * containing new text intervals, such that each text interval is associated with * a single level run.</p> * @param levels array of levels each corresponding to each index of the delimited * text range */
private void assignLevels(int[] levels) { Vector intervalsNew = new Vector(intervals.size()); for (Object interval : intervals) { TextInterval ti = (TextInterval) interval; intervalsNew.addAll(assignLevels(ti, levels)); } if (!intervalsNew.equals(intervals)) { intervals = intervalsNew; } }

Assign resolved levels to a specified text interval over this delimited text range.

Returns a list of text intervals containing either (1) the single, input text interval or (2) two or more new text intervals obtained from sub-dividing the input text range into level runs, i.e., runs of text assigned to a single level.

Params:
  • ti – a text interval to which levels are to be assigned
  • levels – array of levels each corresponding to each index of the delimited text range
Returns:a list of text intervals as described above
/** * <p>Assign resolved levels to a specified text interval over this delimited text * range.</p> * <p>Returns a list of text intervals containing either (1) the single, input text * interval or (2) two or more new text intervals obtained from sub-dividing the input * text range into level runs, i.e., runs of text assigned to a single level.</p> * @param ti a text interval to which levels are to be assigned * @param levels array of levels each corresponding to each index of the delimited * text range * @return a list of text intervals as described above */
private static final Log log = LogFactory.getLog(BidiResolver.class); private List assignLevels(TextInterval ti, int[] levels) { Vector tiv = new Vector(); FONode fn = ti.getNode(); int fnStart = ti.getStart(); // start of node's text in delimited text range for (int i = fnStart, n = ti.getEnd(); i < n; ) { int s = i; // inclusive start index of interval in delimited text range int e = s; // exclusive end index of interval in delimited text range int l = levels [ s ]; // current run level while (e < n) { // skip to end of run level or end of interval if (levels [ e ] != l) { break; } else { e++; } } if ((ti.getStart() == s) && (ti.getEnd() == e)) { ti.setLevel(l); // reuse interval, assigning it single level } else { ti = new TextInterval(fn, fnStart, s, e, l); // subdivide interval } if (log.isDebugEnabled()) { log.debug("AL(" + l + "): " + ti); } tiv.add(ti); i = e; } return tiv; }

Assign resolved levels for each interval to source #PCDATA in the associated FOText.

/** * <p>Assign resolved levels for each interval to source #PCDATA in the associated FOText.</p> */
private void assignTextLevels() { for (Object interval : intervals) { TextInterval ti = (TextInterval) interval; ti.assignTextLevels(); } } private void assignBlockLevel(Direction paragraphEmbeddingLevel) { int defaultLevel = (paragraphEmbeddingLevel == Direction.RL) ? 1 : 0; for (Object interval : intervals) { TextInterval ti = (TextInterval) interval; assignBlockLevel(ti.getNode(), defaultLevel); } } private void assignBlockLevel(FONode node, int defaultLevel) { for (FONode fn = node; fn != null; fn = fn.getParent()) { if (fn instanceof FObj) { FObj fo = (FObj) fn; if (fo.isBidiRangeBlockItem()) { if (fo.getBidiLevel() < 0) { fo.setBidiLevel(defaultLevel); } break; } } } } }