/* * 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 <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. * @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. * @return node that generated this text range */ public FONode getNode() { return fn; } /** * 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. * @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. * @return true if range is empty */ public boolean isEmpty() { return buffer.length() == 0; } /** * 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(); } } /** * <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; } } /** * <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; } /** * <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; } } } } }