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.

WordArea.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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.area.inline;
  19. import java.util.Arrays;
  20. import java.util.List;
  21. import org.apache.fop.complexscripts.bidi.InlineRun;
  22. import org.apache.fop.complexscripts.util.CharMirror;
  23. /**
  24. * A string of characters without spaces
  25. */
  26. public class WordArea extends InlineArea {
  27. private static final long serialVersionUID = 6444644662158970942L;
  28. /** The text for this word area */
  29. protected String word;
  30. /** An array of width for adjusting the individual letters (optional) */
  31. protected int[] letterAdjust;
  32. /**
  33. * An array of resolved bidirectional levels corresponding to each character
  34. * in word (optional)
  35. */
  36. protected int[] levels;
  37. /**
  38. * An array of glyph positioning adjustments to apply to each glyph 'char' in word (optional)
  39. */
  40. protected int[][] gposAdjustments;
  41. /**
  42. * A flag indicating whether the content of word is reversed in relation to
  43. * its original logical order.
  44. */
  45. protected boolean reversed;
  46. /**
  47. * Create a word area
  48. * @param blockProgressionOffset the offset for this area
  49. * @param level the bidirectional embedding level (or -1 if not defined) for word as a group
  50. * @param word the word string
  51. * @param letterAdjust the letter adjust array (may be null)
  52. * @param levels array of per-character (glyph) bidirectional levels,
  53. * in case word area is heterogenously leveled
  54. * @param gposAdjustments array of general position adjustments or null if none apply
  55. * @param reversed true if word is known to be reversed at construction time
  56. */
  57. public WordArea(
  58. int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
  59. int[][] gposAdjustments, boolean reversed) {
  60. super(blockProgressionOffset, level);
  61. int length = (word != null) ? word.length() : 0;
  62. this.word = word;
  63. this.letterAdjust = maybeAdjustLength(letterAdjust, length);
  64. this.levels = maybePopulateLevels(levels, level, length);
  65. this.gposAdjustments = maybeAdjustLength(gposAdjustments, length);
  66. this.reversed = reversed;
  67. }
  68. /**
  69. * Create a word area
  70. * @param blockProgressionOffset the offset for this area
  71. * @param level the bidirectional embedding level (or -1 if not defined) for word as a group
  72. * @param word the word string
  73. * @param letterAdjust the letter adjust array (may be null)
  74. * @param levels array of per-character (glyph) bidirectional levels,
  75. * in case word area is heterogenously leveled
  76. * @param gposAdjustments array of general position adjustments or null if none apply
  77. */
  78. public WordArea(
  79. int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
  80. int[][] gposAdjustments) {
  81. this (blockProgressionOffset, level, word, letterAdjust, levels, gposAdjustments, false);
  82. }
  83. /** @return Returns the word. */
  84. public String getWord() {
  85. return word;
  86. }
  87. /** @return the array of letter adjust widths */
  88. public int[] getLetterAdjustArray() {
  89. return this.letterAdjust;
  90. }
  91. /**
  92. * Obtain per-character (glyph) bidi levels.
  93. * @return a (possibly empty) array of levels or null (if none resolved)
  94. */
  95. public int[] getBidiLevels() {
  96. return levels;
  97. }
  98. /**
  99. * <p>Obtain per-character (glyph) bidi levels over a specified subsequence.</p>
  100. * <p>If word has been reversed, then the subsequence is over the reversed word.</p>
  101. * @param start starting (inclusive) index of subsequence
  102. * @param end ending (exclusive) index of subsequence
  103. * @return a (possibly null) array of per-character (glyph) levels over the specified
  104. * sequence
  105. */
  106. public int[] getBidiLevels(int start, int end) {
  107. assert start <= end;
  108. if (this.levels != null) {
  109. int n = end - start;
  110. int[] levels = new int [ n ];
  111. for (int i = 0; i < n; i++) {
  112. levels[i] = this.levels [ start + i ];
  113. }
  114. return levels;
  115. } else {
  116. return null;
  117. }
  118. }
  119. /**
  120. * <p>Obtain per-character (glyph) level at a specified index position.</p>
  121. * <p>If word has been reversed, then the position is relative to the reversed word.</p>
  122. * @param position the index of the (possibly reversed) character from which to obtain the
  123. * level
  124. * @return a resolved bidirectional level or, if not specified, then -1
  125. */
  126. public int bidiLevelAt(int position) {
  127. if (position > word.length()) {
  128. throw new IndexOutOfBoundsException();
  129. } else if (levels != null) {
  130. return levels [ position ];
  131. } else {
  132. return -1;
  133. }
  134. }
  135. @Override
  136. public List collectInlineRuns(List runs) {
  137. assert runs != null;
  138. InlineRun r;
  139. int[] levels = getBidiLevels();
  140. if ((levels != null) && (levels.length > 0)) {
  141. r = new InlineRun(this, levels);
  142. } else {
  143. r = new InlineRun(this, getBidiLevel(), word.length());
  144. }
  145. runs.add(r);
  146. return runs;
  147. }
  148. /**
  149. * Obtain per-character (glyph) position adjustments.
  150. * @return a (possibly empty) array of adjustments, each having four elements, or null
  151. * if no adjustments apply
  152. */
  153. public int[][] getGlyphPositionAdjustments() {
  154. return gposAdjustments;
  155. }
  156. /**
  157. * <p>Obtain per-character (glyph) position adjustments at a specified index position.</p>
  158. * <p>If word has been reversed, then the position is relative to the reversed word.</p>
  159. * @param position the index of the (possibly reversed) character from which to obtain the
  160. * level
  161. * @return an array of adjustments or null if none applies
  162. */
  163. public int[] glyphPositionAdjustmentsAt(int position) {
  164. if (position > word.length()) {
  165. throw new IndexOutOfBoundsException();
  166. } else if (gposAdjustments != null) {
  167. return gposAdjustments [ position ];
  168. } else {
  169. return null;
  170. }
  171. }
  172. /**
  173. * <p>Reverse characters and corresponding per-character levels and glyph position
  174. * adjustments.</p>
  175. * @param mirror if true, then perform mirroring if mirrorred characters
  176. */
  177. public void reverse(boolean mirror) {
  178. if (word.length() > 0) {
  179. word = ((new StringBuffer(word)) .reverse()) .toString();
  180. if (levels != null) {
  181. reverse(levels);
  182. }
  183. if (gposAdjustments != null) {
  184. reverse(gposAdjustments);
  185. }
  186. reversed = !reversed;
  187. if (mirror) {
  188. word = CharMirror.mirror(word);
  189. }
  190. }
  191. }
  192. /**
  193. * <p>Perform mirroring on mirrorable characters.</p>
  194. */
  195. public void mirror() {
  196. if (word.length() > 0) {
  197. word = CharMirror.mirror(word);
  198. }
  199. }
  200. /**
  201. * <p>Determined if word has been reversed (in relation to original logical order).</p>
  202. * <p>If a word is reversed, then both its characters (glyphs) and corresponding per-character
  203. * levels are in reverse order.</p>
  204. * <p>Note: this information is used in order to process non-spacing marks during rendering as
  205. * well as provide hints for caret direction.</p>
  206. * @return true if word is reversed
  207. */
  208. public boolean isReversed() {
  209. return reversed;
  210. }
  211. /*
  212. * If int[] array is not of specified length, then create
  213. * a new copy of the first length entries.
  214. */
  215. private static int[] maybeAdjustLength(int[] ia, int length) {
  216. if (ia != null) {
  217. if (ia.length == length) {
  218. return ia;
  219. } else {
  220. int[] iaNew = new int [ length ];
  221. for (int i = 0, n = ia.length; i < n; i++) {
  222. if (i < length) {
  223. iaNew [ i ] = ia [ i ];
  224. } else {
  225. break;
  226. }
  227. }
  228. return iaNew;
  229. }
  230. } else {
  231. return ia;
  232. }
  233. }
  234. /*
  235. * If int[][] matrix is not of specified length, then create
  236. * a new shallow copy of the first length entries.
  237. */
  238. private static int[][] maybeAdjustLength(int[][] im, int length) {
  239. if (im != null) {
  240. if (im.length == length) {
  241. return im;
  242. } else {
  243. int[][] imNew = new int [ length ][];
  244. for (int i = 0, n = im.length; i < n; i++) {
  245. if (i < length) {
  246. imNew [ i ] = im [ i ];
  247. } else {
  248. break;
  249. }
  250. }
  251. return imNew;
  252. }
  253. } else {
  254. return im;
  255. }
  256. }
  257. private static int[] maybePopulateLevels(int[] levels, int level, int count) {
  258. if ((levels == null) && (level >= 0)) {
  259. levels = new int[count];
  260. Arrays.fill(levels, level);
  261. }
  262. return maybeAdjustLength(levels, count);
  263. }
  264. private static void reverse(int[] a) {
  265. for (int i = 0, n = a.length, m = n / 2; i < m; i++) {
  266. int k = n - i - 1;
  267. int t = a [ k ];
  268. a [ k ] = a [ i ];
  269. a [ i ] = t;
  270. }
  271. }
  272. private static void reverse(int[][] aa) {
  273. for (int i = 0, n = aa.length, m = n / 2; i < m; i++) {
  274. int k = n - i - 1;
  275. int[] t = aa [ k ];
  276. aa [ k ] = aa [ i ];
  277. aa [ i ] = t;
  278. }
  279. }
  280. }