You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

InlineRun.java 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.complexscripts.bidi;
  19. import java.util.Arrays;
  20. import java.util.List;
  21. import java.util.Vector;
  22. import org.apache.fop.area.inline.Anchor;
  23. import org.apache.fop.area.inline.InlineArea;
  24. import org.apache.fop.area.inline.InlineBlockParent;
  25. import org.apache.fop.area.inline.InlineParent;
  26. import org.apache.fop.area.inline.InlineViewport;
  27. import org.apache.fop.area.inline.Leader;
  28. import org.apache.fop.area.inline.Space;
  29. import org.apache.fop.area.inline.SpaceArea;
  30. import org.apache.fop.area.inline.UnresolvedPageNumber;
  31. import org.apache.fop.area.inline.WordArea;
  32. import org.apache.fop.util.CharUtilities;
  33. /**
  34. * The <code>InlineRun</code> class is a utility class, the instances of which are used
  35. * to capture a sequence of reordering levels associated with an inline area.
  36. *
  37. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  38. */
  39. public class InlineRun {
  40. private InlineArea inline;
  41. private int[] levels;
  42. private int minLevel;
  43. private int maxLevel;
  44. private int reversals;
  45. /**
  46. * Primary constructor.
  47. * @param inline which generated this inline run
  48. * @param levels levels array
  49. */
  50. public InlineRun(InlineArea inline, int[] levels) {
  51. assert inline != null;
  52. assert levels != null;
  53. this.inline = inline;
  54. this.levels = levels;
  55. setMinMax(levels);
  56. }
  57. /**
  58. * Alternate constructor.
  59. * @param inline which generated this inline run
  60. * @param level for each index
  61. * @param count of indices
  62. */
  63. public InlineRun(InlineArea inline, int level, int count) {
  64. this (inline, makeLevels(level, count));
  65. }
  66. /**
  67. * Obtain inline area that generated this inline run.
  68. * @return inline area that generated this inline run.
  69. */
  70. public InlineArea getInline() {
  71. return inline;
  72. }
  73. /**
  74. * Obtain minimum bidi level for this run.
  75. * @return minimum bidi level
  76. */
  77. public int getMinLevel() {
  78. return minLevel;
  79. }
  80. /**
  81. * Obtain maximum bidi level for this run.
  82. * @return maximum bidi level
  83. */
  84. public int getMaxLevel() {
  85. return maxLevel;
  86. }
  87. private void setMinMax(int[] levels) {
  88. int mn = Integer.MAX_VALUE;
  89. int mx = Integer.MIN_VALUE;
  90. if ((levels != null) && (levels.length > 0)) {
  91. for (int i = 0, n = levels.length; i < n; i++) {
  92. int l = levels [ i ];
  93. if (l < mn) {
  94. mn = l;
  95. }
  96. if (l > mx) {
  97. mx = l;
  98. }
  99. }
  100. } else {
  101. mn = mx = -1;
  102. }
  103. this.minLevel = mn;
  104. this.maxLevel = mx;
  105. }
  106. /**
  107. * Determine if this run has homogenous (same valued) bidi levels.
  108. * @return true if homogenous
  109. */
  110. public boolean isHomogenous() {
  111. return minLevel == maxLevel;
  112. }
  113. /**
  114. * Split this inline run into homogenous runs.
  115. * @return list of new runs
  116. */
  117. public List split() {
  118. List runs = new Vector();
  119. for (int i = 0, n = levels.length; i < n; ) {
  120. int l = levels [ i ];
  121. int s = i;
  122. int e = s;
  123. while (e < n) {
  124. if (levels [ e ] != l) {
  125. break;
  126. } else {
  127. e++;
  128. }
  129. }
  130. if (s < e) {
  131. runs.add(new InlineRun(inline, l, e - s));
  132. }
  133. i = e;
  134. }
  135. assert runs.size() < 2 : "heterogeneous inlines not yet supported!!";
  136. return runs;
  137. }
  138. /**
  139. * Update a min/max array to correspond with this run's min/max values.
  140. * @param mm reference to min/max array
  141. */
  142. public void updateMinMax(int[] mm) {
  143. if (minLevel < mm[0]) {
  144. mm[0] = minLevel;
  145. }
  146. if (maxLevel > mm[1]) {
  147. mm[1] = maxLevel;
  148. }
  149. }
  150. /**
  151. * Determine if run needs mirroring.
  152. * @return true if run is homogenous and (positive) odd (i.e., right to left)
  153. */
  154. public boolean maybeNeedsMirroring() {
  155. return (minLevel == maxLevel) && (minLevel > 0) && ((minLevel & 1) != 0);
  156. }
  157. /**
  158. * Reverse run (by incrementing reversal count, not actually reversing).
  159. */
  160. public void reverse() {
  161. reversals++;
  162. }
  163. /**
  164. * Reverse inline area if it is a word area and it requires
  165. * reversal.
  166. * @param mirror if true then also mirror characters
  167. */
  168. public void maybeReverseWord(boolean mirror) {
  169. if (inline instanceof WordArea) {
  170. WordArea w = (WordArea) inline;
  171. // if not already reversed, then reverse now
  172. if (!w.isReversed()) {
  173. if ((reversals & 1) != 0) {
  174. w.reverse(mirror);
  175. } else if (mirror && maybeNeedsMirroring()) {
  176. w.mirror();
  177. }
  178. }
  179. }
  180. }
  181. @Override
  182. public boolean equals(Object o) {
  183. if (o instanceof InlineRun) {
  184. InlineRun ir = (InlineRun) o;
  185. if (ir.inline != inline) {
  186. return false;
  187. } else if (ir.minLevel != minLevel) {
  188. return false;
  189. } else if (ir.maxLevel != maxLevel) {
  190. return false;
  191. } else if ((ir.levels != null) && (levels != null)) {
  192. if (ir.levels.length != levels.length) {
  193. return false;
  194. } else {
  195. for (int i = 0, n = levels.length; i < n; i++) {
  196. if (ir.levels[i] != levels[i]) {
  197. return false;
  198. }
  199. }
  200. return true;
  201. }
  202. } else {
  203. return (ir.levels == null) && (levels == null);
  204. }
  205. } else {
  206. return false;
  207. }
  208. }
  209. @Override
  210. public int hashCode() {
  211. int l = (inline != null) ? inline.hashCode() : 0;
  212. l = (l ^ minLevel) + (l << 19);
  213. l = (l ^ maxLevel) + (l << 11);
  214. return l;
  215. }
  216. @Override
  217. public String toString() {
  218. StringBuffer sb = new StringBuffer("RR: { type = \'");
  219. char c;
  220. String content = null;
  221. if (inline instanceof WordArea) {
  222. c = 'W';
  223. content = ((WordArea) inline) .getWord();
  224. } else if (inline instanceof SpaceArea) {
  225. c = 'S';
  226. content = ((SpaceArea) inline) .getSpace();
  227. } else if (inline instanceof Anchor) {
  228. c = 'A';
  229. } else if (inline instanceof Leader) {
  230. c = 'L';
  231. } else if (inline instanceof Space) {
  232. c = 'S';
  233. } else if (inline instanceof UnresolvedPageNumber) {
  234. c = '#';
  235. content = ((UnresolvedPageNumber) inline) .getText();
  236. } else if (inline instanceof InlineBlockParent) {
  237. c = 'B';
  238. } else if (inline instanceof InlineViewport) {
  239. c = 'V';
  240. } else if (inline instanceof InlineParent) {
  241. c = 'I';
  242. } else {
  243. c = '?';
  244. }
  245. sb.append(c);
  246. sb.append("\', levels = \'");
  247. sb.append(generateLevels(levels));
  248. sb.append("\', min = ");
  249. sb.append(minLevel);
  250. sb.append(", max = ");
  251. sb.append(maxLevel);
  252. sb.append(", reversals = ");
  253. sb.append(reversals);
  254. sb.append(", content = <");
  255. sb.append(CharUtilities.toNCRefs(content));
  256. sb.append("> }");
  257. return sb.toString();
  258. }
  259. private String generateLevels(int[] levels) {
  260. StringBuffer lb = new StringBuffer();
  261. int maxLevel = -1;
  262. int numLevels = levels.length;
  263. for (int i = 0; i < numLevels; i++) {
  264. int l = levels [ i ];
  265. if (l > maxLevel) {
  266. maxLevel = l;
  267. }
  268. }
  269. if (maxLevel < 0) {
  270. // leave level buffer empty
  271. } else if (maxLevel < 10) {
  272. // use string of decimal digits
  273. for (int i = 0; i < numLevels; i++) {
  274. lb.append((char) ('0' + levels [ i ]));
  275. }
  276. } else {
  277. // use comma separated list
  278. boolean first = true;
  279. for (int i = 0; i < numLevels; i++) {
  280. if (first) {
  281. first = false;
  282. } else {
  283. lb.append(',');
  284. }
  285. lb.append(levels [ i ]);
  286. }
  287. }
  288. return lb.toString();
  289. }
  290. private static int[] makeLevels(int level, int count) {
  291. int[] levels = new int [ count > 0 ? count : 1 ];
  292. Arrays.fill(levels, level);
  293. return levels;
  294. }
  295. }