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 11KB

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