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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  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.util;
  19. import java.nio.IntBuffer;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. // CSOFF: LineLengthCheck
  23. /**
  24. * <p>A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes,
  25. * and a sequence of character associations, where, for each glyph in the sequence of glyph
  26. * codes, there is a corresponding character association. Character associations server to
  27. * relate the glyph codes in a glyph sequence to the specific characters in an original
  28. * character code sequence with which the glyph codes are associated.</p>
  29. *
  30. * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
  31. */
  32. public class GlyphSequence implements Cloneable {
  33. /** default character buffer capacity in case new character buffer is created */
  34. private static final int DEFAULT_CHARS_CAPACITY = 8;
  35. /** character buffer */
  36. private IntBuffer characters;
  37. /** glyph buffer */
  38. private IntBuffer glyphs;
  39. /** association list */
  40. private List associations;
  41. /** predications flag */
  42. private boolean predications;
  43. /**
  44. * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
  45. * character and glyph buffers and associations. If characters is null, then
  46. * an empty character buffer is created. If glyphs is null, then a glyph buffer
  47. * is created whose capacity is that of the character buffer. If associations is
  48. * null, then identity associations are created.
  49. * @param characters a (possibly null) buffer of associated (originating) characters
  50. * @param glyphs a (possibly null) buffer of glyphs
  51. * @param associations a (possibly null) array of glyph to character associations
  52. * @param predications true if predications are enabled
  53. */
  54. public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations, boolean predications) {
  55. if (characters == null) {
  56. characters = IntBuffer.allocate(DEFAULT_CHARS_CAPACITY);
  57. }
  58. if (glyphs == null) {
  59. glyphs = IntBuffer.allocate(characters.capacity());
  60. }
  61. if (associations == null) {
  62. associations = makeIdentityAssociations(characters.limit(), glyphs.limit());
  63. }
  64. this.characters = characters;
  65. this.glyphs = glyphs;
  66. this.associations = associations;
  67. this.predications = predications;
  68. }
  69. /**
  70. * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
  71. * character and glyph buffers and associations. If characters is null, then
  72. * an empty character buffer is created. If glyphs is null, then a glyph buffer
  73. * is created whose capacity is that of the character buffer. If associations is
  74. * null, then identity associations are created.
  75. * @param characters a (possibly null) buffer of associated (originating) characters
  76. * @param glyphs a (possibly null) buffer of glyphs
  77. * @param associations a (possibly null) array of glyph to character associations
  78. */
  79. public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations) {
  80. this (characters, glyphs, associations, false);
  81. }
  82. /**
  83. * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
  84. * the character array of the existing sequence (but not the buffer object), and creates new copies
  85. * of glyphs buffer and association list.
  86. * @param gs an existing glyph sequence
  87. */
  88. public GlyphSequence(GlyphSequence gs) {
  89. this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications);
  90. }
  91. /**
  92. * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
  93. * the character array of the existing sequence (but not the buffer object), but uses the specified
  94. * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified
  95. * of glyphs buffer and association list.
  96. * backtrack, input, and lookahead association arrays to populate the associations.
  97. * @param gs an existing glyph sequence
  98. * @param bga backtrack glyph array
  99. * @param iga input glyph array
  100. * @param lga lookahead glyph array
  101. * @param bal backtrack association list
  102. * @param ial input association list
  103. * @param lal lookahead association list
  104. */
  105. public GlyphSequence(GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal) {
  106. this (gs.characters.duplicate(), concatGlyphs(bga, iga, lga), concatAssociations(bal, ial, lal), gs.predications);
  107. }
  108. /**
  109. * Obtain reference to underlying character buffer.
  110. * @return character buffer reference
  111. */
  112. public IntBuffer getCharacters() {
  113. return characters;
  114. }
  115. /**
  116. * Obtain array of characters. If <code>copy</code> is true, then
  117. * a newly instantiated array is returned, otherwise a reference to
  118. * the underlying buffer's array is returned. N.B. in case a reference
  119. * to the undelying buffer's array is returned, the length
  120. * of the array is not necessarily the number of characters in array.
  121. * To determine the number of characters, use {@link #getCharacterCount}.
  122. * @param copy true if to return a newly instantiated array of characters
  123. * @return array of characters
  124. */
  125. public int[] getCharacterArray(boolean copy) {
  126. if (copy) {
  127. return toArray(characters);
  128. } else {
  129. return characters.array();
  130. }
  131. }
  132. /**
  133. * Obtain the number of characters in character array, where
  134. * each character constitutes a unicode scalar value.
  135. * @return number of characters available in character array
  136. */
  137. public int getCharacterCount() {
  138. return characters.limit();
  139. }
  140. /**
  141. * Obtain glyph id at specified index.
  142. * @param index to obtain glyph
  143. * @return the glyph identifier of glyph at specified index
  144. * @throws IndexOutOfBoundsException if index is less than zero
  145. * or exceeds last valid position
  146. */
  147. public int getGlyph(int index) throws IndexOutOfBoundsException {
  148. return glyphs.get(index);
  149. }
  150. /**
  151. * Set glyph id at specified index.
  152. * @param index to set glyph
  153. * @param gi glyph index
  154. * @throws IndexOutOfBoundsException if index is greater or equal to
  155. * the limit of the underlying glyph buffer
  156. */
  157. public void setGlyph(int index, int gi) throws IndexOutOfBoundsException {
  158. if (gi > 65535) {
  159. gi = 65535;
  160. }
  161. glyphs.put(index, gi);
  162. }
  163. /**
  164. * Obtain reference to underlying glyph buffer.
  165. * @return glyph buffer reference
  166. */
  167. public IntBuffer getGlyphs() {
  168. return glyphs;
  169. }
  170. /**
  171. * Obtain count glyphs starting at offset. If <code>count</code> is
  172. * negative, then it is treated as if the number of available glyphs
  173. * were specified.
  174. * @param offset into glyph sequence
  175. * @param count of glyphs to obtain starting at offset, or negative,
  176. * indicating all avaialble glyphs starting at offset
  177. * @return glyph array
  178. */
  179. public int[] getGlyphs(int offset, int count) {
  180. int ng = getGlyphCount();
  181. if (offset < 0) {
  182. offset = 0;
  183. } else if (offset > ng) {
  184. offset = ng;
  185. }
  186. if (count < 0) {
  187. count = ng - offset;
  188. }
  189. int[] ga = new int [ count ];
  190. for (int i = offset, n = offset + count, k = 0; i < n; i++) {
  191. if (k < ga.length) {
  192. ga [ k++ ] = glyphs.get(i);
  193. }
  194. }
  195. return ga;
  196. }
  197. /**
  198. * Obtain array of glyphs. If <code>copy</code> is true, then
  199. * a newly instantiated array is returned, otherwise a reference to
  200. * the underlying buffer's array is returned. N.B. in case a reference
  201. * to the undelying buffer's array is returned, the length
  202. * of the array is not necessarily the number of glyphs in array.
  203. * To determine the number of glyphs, use {@link #getGlyphCount}.
  204. * @param copy true if to return a newly instantiated array of glyphs
  205. * @return array of glyphs
  206. */
  207. public int[] getGlyphArray(boolean copy) {
  208. if (copy) {
  209. return toArray(glyphs);
  210. } else {
  211. return glyphs.array();
  212. }
  213. }
  214. /**
  215. * Obtain the number of glyphs in glyphs array, where
  216. * each glyph constitutes a font specific glyph index.
  217. * @return number of glyphs available in character array
  218. */
  219. public int getGlyphCount() {
  220. return glyphs.limit();
  221. }
  222. /**
  223. * Obtain association at specified index.
  224. * @param index into associations array
  225. * @return glyph to character associations at specified index
  226. * @throws IndexOutOfBoundsException if index is less than zero
  227. * or exceeds last valid position
  228. */
  229. public CharAssociation getAssociation(int index) throws IndexOutOfBoundsException {
  230. return (CharAssociation) associations.get(index);
  231. }
  232. /**
  233. * Obtain reference to underlying associations list.
  234. * @return associations list
  235. */
  236. public List getAssociations() {
  237. return associations;
  238. }
  239. /**
  240. * Obtain count associations starting at offset.
  241. * @param offset into glyph sequence
  242. * @param count of associations to obtain starting at offset, or negative,
  243. * indicating all avaialble associations starting at offset
  244. * @return associations
  245. */
  246. public CharAssociation[] getAssociations(int offset, int count) {
  247. int ng = getGlyphCount();
  248. if (offset < 0) {
  249. offset = 0;
  250. } else if (offset > ng) {
  251. offset = ng;
  252. }
  253. if (count < 0) {
  254. count = ng - offset;
  255. }
  256. CharAssociation[] aa = new CharAssociation [ count ];
  257. for (int i = offset, n = offset + count, k = 0; i < n; i++) {
  258. if (k < aa.length) {
  259. aa [ k++ ] = (CharAssociation) associations.get(i);
  260. }
  261. }
  262. return aa;
  263. }
  264. /**
  265. * Enable or disable predications.
  266. * @param enable true if predications are to be enabled; otherwise false to disable
  267. */
  268. public void setPredications(boolean enable) {
  269. this.predications = enable;
  270. }
  271. /**
  272. * Obtain predications state.
  273. * @return true if predications are enabled
  274. */
  275. public boolean getPredications() {
  276. return this.predications;
  277. }
  278. /**
  279. * Set predication <KEY,VALUE> at glyph sequence OFFSET.
  280. * @param offset offset (index) into glyph sequence
  281. * @param key predication key
  282. * @param value predication value
  283. */
  284. public void setPredication(int offset, String key, Object value) {
  285. if (predications) {
  286. CharAssociation[] aa = getAssociations(offset, 1);
  287. CharAssociation ca = aa[0];
  288. ca.setPredication(key, value);
  289. }
  290. }
  291. /**
  292. * Get predication KEY at glyph sequence OFFSET.
  293. * @param offset offset (index) into glyph sequence
  294. * @param key predication key
  295. * @return predication KEY at OFFSET or null if none exists
  296. */
  297. public Object getPredication(int offset, String key) {
  298. if (predications) {
  299. CharAssociation[] aa = getAssociations(offset, 1);
  300. CharAssociation ca = aa[0];
  301. return ca.getPredication(key);
  302. } else {
  303. return null;
  304. }
  305. }
  306. /**
  307. * Compare glyphs.
  308. * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared
  309. * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
  310. * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
  311. */
  312. public int compareGlyphs(IntBuffer gb) {
  313. int ng = getGlyphCount();
  314. for (int i = 0, n = gb.limit(); i < n; i++) {
  315. if (i < ng) {
  316. int g1 = glyphs.get(i);
  317. int g2 = gb.get(i);
  318. if (g1 > g2) {
  319. return 1;
  320. } else if (g1 < g2) {
  321. return -1;
  322. }
  323. } else {
  324. return -1; // this gb is a proper prefix of specified gb
  325. }
  326. }
  327. return 0; // same lengths with no difference
  328. }
  329. /** {@inheritDoc} */
  330. public Object clone() {
  331. try {
  332. GlyphSequence gs = (GlyphSequence) super.clone();
  333. gs.characters = copyBuffer(characters);
  334. gs.glyphs = copyBuffer(glyphs);
  335. gs.associations = copyAssociations(associations);
  336. return gs;
  337. } catch (CloneNotSupportedException e) {
  338. return null;
  339. }
  340. }
  341. /** {@inheritDoc} */
  342. public String toString() {
  343. StringBuffer sb = new StringBuffer();
  344. sb.append('{');
  345. sb.append("chars = [");
  346. sb.append(characters);
  347. sb.append("], glyphs = [");
  348. sb.append(glyphs);
  349. sb.append("], associations = [");
  350. sb.append(associations);
  351. sb.append("]");
  352. sb.append('}');
  353. return sb.toString();
  354. }
  355. /**
  356. * Determine if two arrays of glyphs are identical.
  357. * @param ga1 first glyph array
  358. * @param ga2 second glyph array
  359. * @return true if arrays are botth null or both non-null and have identical elements
  360. */
  361. public static boolean sameGlyphs(int[] ga1, int[] ga2) {
  362. if (ga1 == ga2) {
  363. return true;
  364. } else if ((ga1 == null) || (ga2 == null)) {
  365. return false;
  366. } else if (ga1.length != ga2.length) {
  367. return false;
  368. } else {
  369. for (int i = 0, n = ga1.length; i < n; i++) {
  370. if (ga1[i] != ga2[i]) {
  371. return false;
  372. }
  373. }
  374. return true;
  375. }
  376. }
  377. /**
  378. * Concatenante glyph arrays.
  379. * @param bga backtrack glyph array
  380. * @param iga input glyph array
  381. * @param lga lookahead glyph array
  382. * @return new integer buffer containing concatenated glyphs
  383. */
  384. public static IntBuffer concatGlyphs(int[] bga, int[] iga, int[] lga) {
  385. int ng = 0;
  386. if (bga != null) {
  387. ng += bga.length;
  388. }
  389. if (iga != null) {
  390. ng += iga.length;
  391. }
  392. if (lga != null) {
  393. ng += lga.length;
  394. }
  395. IntBuffer gb = IntBuffer.allocate(ng);
  396. if (bga != null) {
  397. gb.put(bga);
  398. }
  399. if (iga != null) {
  400. gb.put(iga);
  401. }
  402. if (lga != null) {
  403. gb.put(lga);
  404. }
  405. gb.flip();
  406. return gb;
  407. }
  408. /**
  409. * Concatenante association arrays.
  410. * @param baa backtrack association array
  411. * @param iaa input association array
  412. * @param laa lookahead association array
  413. * @return new list containing concatenated associations
  414. */
  415. public static List concatAssociations(CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa) {
  416. int na = 0;
  417. if (baa != null) {
  418. na += baa.length;
  419. }
  420. if (iaa != null) {
  421. na += iaa.length;
  422. }
  423. if (laa != null) {
  424. na += laa.length;
  425. }
  426. if (na > 0) {
  427. List gl = new ArrayList(na);
  428. if (baa != null) {
  429. for (CharAssociation aBaa : baa) {
  430. gl.add(aBaa);
  431. }
  432. }
  433. if (iaa != null) {
  434. for (CharAssociation anIaa : iaa) {
  435. gl.add(anIaa);
  436. }
  437. }
  438. if (laa != null) {
  439. for (CharAssociation aLaa : laa) {
  440. gl.add(aLaa);
  441. }
  442. }
  443. return gl;
  444. } else {
  445. return null;
  446. }
  447. }
  448. /**
  449. * Join (concatenate) glyph sequences.
  450. * @param gs original glyph sequence from which to reuse character array reference
  451. * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated
  452. * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA
  453. */
  454. public static GlyphSequence join(GlyphSequence gs, GlyphSequence[] sa) {
  455. assert sa != null;
  456. int tg = 0;
  457. int ta = 0;
  458. for (GlyphSequence s : sa) {
  459. IntBuffer ga = s.getGlyphs();
  460. assert ga != null;
  461. int ng = ga.limit();
  462. List al = s.getAssociations();
  463. assert al != null;
  464. int na = al.size();
  465. assert na == ng;
  466. tg += ng;
  467. ta += na;
  468. }
  469. IntBuffer uga = IntBuffer.allocate(tg);
  470. ArrayList ual = new ArrayList(ta);
  471. for (GlyphSequence s : sa) {
  472. uga.put(s.getGlyphs());
  473. ual.addAll(s.getAssociations());
  474. }
  475. return new GlyphSequence(gs.getCharacters(), uga, ual, gs.getPredications());
  476. }
  477. /**
  478. * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET.
  479. * @param gs input sequence
  480. * @param source index of sub-sequence to reorder
  481. * @param count length of sub-sequence to reorder
  482. * @param target index to which source sub-sequence is to be moved
  483. * @return reordered sequence (or original if no reordering performed)
  484. */
  485. public static GlyphSequence reorder(GlyphSequence gs, int source, int count, int target) {
  486. if (source != target) {
  487. int ng = gs.getGlyphCount();
  488. int[] ga = gs.getGlyphArray(false);
  489. int[] nga = new int [ ng ];
  490. CharAssociation[] aa = gs.getAssociations(0, ng);
  491. CharAssociation[] naa = new CharAssociation [ ng ];
  492. if (source < target) {
  493. int t = 0;
  494. for (int s = 0, e = source; s < e; s++, t++) {
  495. nga[t] = ga[s];
  496. naa[t] = aa[s];
  497. }
  498. for (int s = source + count, e = target; s < e; s++, t++) {
  499. nga[t] = ga[s];
  500. naa[t] = aa[s];
  501. }
  502. for (int s = source, e = source + count; s < e; s++, t++) {
  503. nga[t] = ga[s];
  504. naa[t] = aa[s];
  505. }
  506. for (int s = target, e = ng; s < e; s++, t++) {
  507. nga[t] = ga[s];
  508. naa[t] = aa[s];
  509. }
  510. } else {
  511. int t = 0;
  512. for (int s = 0, e = target; s < e; s++, t++) {
  513. nga[t] = ga[s];
  514. naa[t] = aa[s];
  515. }
  516. for (int s = source, e = source + count; s < e; s++, t++) {
  517. nga[t] = ga[s];
  518. naa[t] = aa[s];
  519. }
  520. for (int s = target, e = source; s < e; s++, t++) {
  521. nga[t] = ga[s];
  522. naa[t] = aa[s];
  523. }
  524. for (int s = source + count, e = ng; s < e; s++, t++) {
  525. nga[t] = ga[s];
  526. naa[t] = aa[s];
  527. }
  528. }
  529. return new GlyphSequence(gs, null, nga, null, null, naa, null);
  530. } else {
  531. return gs;
  532. }
  533. }
  534. private static int[] toArray(IntBuffer ib) {
  535. if (ib != null) {
  536. int n = ib.limit();
  537. int[] ia = new int[n];
  538. ib.get(ia, 0, n);
  539. return ia;
  540. } else {
  541. return new int[0];
  542. }
  543. }
  544. private static List makeIdentityAssociations(int numChars, int numGlyphs) {
  545. int nc = numChars;
  546. int ng = numGlyphs;
  547. List av = new ArrayList(ng);
  548. for (int i = 0, n = ng; i < n; i++) {
  549. int k = (i > nc) ? nc : i;
  550. av.add(new CharAssociation(i, (k == nc) ? 0 : 1));
  551. }
  552. return av;
  553. }
  554. private static IntBuffer copyBuffer(IntBuffer ib) {
  555. if (ib != null) {
  556. int[] ia = new int [ ib.capacity() ];
  557. int p = ib.position();
  558. int l = ib.limit();
  559. System.arraycopy(ib.array(), 0, ia, 0, ia.length);
  560. return IntBuffer.wrap(ia, p, l - p);
  561. } else {
  562. return null;
  563. }
  564. }
  565. private static List copyAssociations(List ca) {
  566. if (ca != null) {
  567. return new ArrayList(ca);
  568. } else {
  569. return ca;
  570. }
  571. }
  572. }