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.

FOText.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833
  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.fo;
  19. import java.awt.Color;
  20. import java.nio.CharBuffer;
  21. import java.util.NoSuchElementException;
  22. import java.util.Stack;
  23. import org.xml.sax.Locator;
  24. import org.apache.fop.accessibility.StructureTreeElement;
  25. import org.apache.fop.apps.FOPException;
  26. import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
  27. import org.apache.fop.datatypes.Length;
  28. import org.apache.fop.fo.flow.Block;
  29. import org.apache.fop.fo.properties.CommonFont;
  30. import org.apache.fop.fo.properties.CommonHyphenation;
  31. import org.apache.fop.fo.properties.CommonTextDecoration;
  32. import org.apache.fop.fo.properties.KeepProperty;
  33. import org.apache.fop.fo.properties.Property;
  34. import org.apache.fop.fo.properties.SpaceProperty;
  35. import org.apache.fop.fonts.TextFragment;
  36. import org.apache.fop.util.CharUtilities;
  37. /**
  38. * A text node (PCDATA) in the formatting object tree.
  39. */
  40. public class FOText extends FONode implements CharSequence, TextFragment {
  41. /** the <code>CharBuffer</code> containing the text */
  42. private CharBuffer charBuffer;
  43. // The value of FO traits (refined properties) that apply to #PCDATA
  44. // (aka implicit sequence of fo:character)
  45. private CommonFont commonFont;
  46. private CommonHyphenation commonHyphenation;
  47. private Color color;
  48. private KeepProperty keepTogether;
  49. private Property letterSpacing;
  50. private SpaceProperty lineHeight;
  51. private int whiteSpaceTreatment;
  52. private int whiteSpaceCollapse;
  53. private int textTransform;
  54. private Property wordSpacing;
  55. private int wrapOption;
  56. private Length baselineShift;
  57. private String country;
  58. private String language;
  59. private String script;
  60. // End of trait values
  61. /**
  62. * Points to the previous FOText object created within the current
  63. * block. If this is "null", this is the first such object.
  64. */
  65. private FOText prevFOTextThisBlock = null;
  66. /**
  67. * Points to the next FOText object created within the current
  68. * block. If this is "null", this is the last such object.
  69. */
  70. private FOText nextFOTextThisBlock = null;
  71. /**
  72. * Points to the ancestor Block object. This is used to keep track of
  73. * which FOText nodes are descendants of the same block.
  74. */
  75. private Block ancestorBlock = null;
  76. /** Holds the text decoration values. May be null */
  77. private CommonTextDecoration textDecoration;
  78. private StructureTreeElement structureTreeElement;
  79. /* bidi levels */
  80. private int[] bidiLevels;
  81. private static final int IS_WORD_CHAR_FALSE = 0;
  82. private static final int IS_WORD_CHAR_TRUE = 1;
  83. private static final int IS_WORD_CHAR_MAYBE = 2;
  84. /**
  85. * Creates a new FO text node.
  86. *
  87. * @param parent FONode that is the parent of this object
  88. */
  89. public FOText(FONode parent) {
  90. super(parent);
  91. }
  92. /** {@inheritDoc} */
  93. protected void characters(char[] data, int start, int length,
  94. PropertyList list, Locator locator) throws FOPException {
  95. if (charBuffer == null) {
  96. // buffer not yet initialized, do so now
  97. int newLength = (length < 16) ? 16 : length;
  98. charBuffer = CharBuffer.allocate(newLength);
  99. } else {
  100. // allocate a larger buffer, and transfer contents
  101. int requires = charBuffer.position() + length;
  102. int capacity = charBuffer.capacity();
  103. if (requires > capacity) {
  104. int newCapacity = capacity * 2;
  105. if (requires > newCapacity) {
  106. newCapacity = requires;
  107. }
  108. CharBuffer newBuffer = CharBuffer.allocate(newCapacity);
  109. charBuffer.rewind();
  110. newBuffer.put(charBuffer);
  111. charBuffer = newBuffer;
  112. }
  113. }
  114. // extend limit to capacity
  115. charBuffer.limit(charBuffer.capacity());
  116. // append characters
  117. charBuffer.put(data, start, length);
  118. // shrink limit to position
  119. charBuffer.limit(charBuffer.position());
  120. }
  121. /**
  122. * Return the array of characters for this instance.
  123. *
  124. * @return a char sequence containing the text
  125. */
  126. public CharSequence getCharSequence() {
  127. if (this.charBuffer == null) {
  128. return null;
  129. }
  130. this.charBuffer.rewind();
  131. return this.charBuffer.asReadOnlyBuffer().subSequence(0, this.charBuffer.limit());
  132. }
  133. /** {@inheritDoc} */
  134. public FONode clone(FONode parent, boolean removeChildren)
  135. throws FOPException {
  136. FOText ft = (FOText) super.clone(parent, removeChildren);
  137. if (removeChildren) {
  138. // not really removing, just make sure the char buffer
  139. // pointed to is really a different one
  140. if (charBuffer != null) {
  141. ft.charBuffer = CharBuffer.allocate(charBuffer.limit());
  142. charBuffer.rewind();
  143. ft.charBuffer.put(charBuffer);
  144. ft.charBuffer.rewind();
  145. }
  146. }
  147. ft.prevFOTextThisBlock = null;
  148. ft.nextFOTextThisBlock = null;
  149. ft.ancestorBlock = null;
  150. return ft;
  151. }
  152. /** {@inheritDoc} */
  153. public void bind(PropertyList pList) throws FOPException {
  154. this.commonFont = pList.getFontProps();
  155. this.commonHyphenation = pList.getHyphenationProps();
  156. this.color = pList.get(Constants.PR_COLOR).getColor(getUserAgent());
  157. this.keepTogether = pList.get(Constants.PR_KEEP_TOGETHER).getKeep();
  158. this.lineHeight = pList.get(Constants.PR_LINE_HEIGHT).getSpace();
  159. this.letterSpacing = pList.get(Constants.PR_LETTER_SPACING);
  160. this.whiteSpaceCollapse = pList.get(Constants.PR_WHITE_SPACE_COLLAPSE).getEnum();
  161. this.whiteSpaceTreatment = pList.get(Constants.PR_WHITE_SPACE_TREATMENT).getEnum();
  162. this.textTransform = pList.get(Constants.PR_TEXT_TRANSFORM).getEnum();
  163. this.wordSpacing = pList.get(Constants.PR_WORD_SPACING);
  164. this.wrapOption = pList.get(Constants.PR_WRAP_OPTION).getEnum();
  165. this.textDecoration = pList.getTextDecorationProps();
  166. this.baselineShift = pList.get(Constants.PR_BASELINE_SHIFT).getLength();
  167. this.country = pList.get(Constants.PR_COUNTRY).getString();
  168. this.language = pList.get(Constants.PR_LANGUAGE).getString();
  169. this.script = pList.get(Constants.PR_SCRIPT).getString();
  170. }
  171. /** {@inheritDoc} */
  172. public void endOfNode() throws FOPException {
  173. if (charBuffer != null) {
  174. charBuffer.rewind();
  175. }
  176. super.endOfNode();
  177. getFOEventHandler().characters(this);
  178. }
  179. /** {@inheritDoc} */
  180. public void finalizeNode() {
  181. textTransform();
  182. }
  183. /**
  184. * Check if this text node will create an area.
  185. * This means either there is non-whitespace or it is
  186. * preserved whitespace.
  187. * Maybe this just needs to check length > 0, since char iterators
  188. * handle whitespace.
  189. *
  190. * @return true if this will create an area in the output
  191. */
  192. public boolean willCreateArea() {
  193. if (whiteSpaceCollapse == Constants.EN_FALSE
  194. && charBuffer.limit() > 0) {
  195. return true;
  196. }
  197. char ch;
  198. charBuffer.rewind();
  199. while (charBuffer.hasRemaining()) {
  200. ch = charBuffer.get();
  201. if (!((ch == CharUtilities.SPACE)
  202. || (ch == CharUtilities.LINEFEED_CHAR)
  203. || (ch == CharUtilities.CARRIAGE_RETURN)
  204. || (ch == CharUtilities.TAB))) {
  205. // not whitespace
  206. charBuffer.rewind();
  207. return true;
  208. }
  209. }
  210. return false;
  211. }
  212. /**
  213. * @return a new TextCharIterator
  214. */
  215. public CharIterator charIterator() {
  216. return new TextCharIterator();
  217. }
  218. /**
  219. * This method is run as part of the ancestor Block's flushText(), to
  220. * create xref pointers to the previous FOText objects within the same Block
  221. * @param ancestorBlock the ancestor fo:block
  222. */
  223. protected void createBlockPointers(Block ancestorBlock) {
  224. this.ancestorBlock = ancestorBlock;
  225. // if the last FOText is a sibling, point to it, and have it point here
  226. if (ancestorBlock.lastFOTextProcessed != null) {
  227. if (ancestorBlock.lastFOTextProcessed.ancestorBlock
  228. == this.ancestorBlock) {
  229. prevFOTextThisBlock = ancestorBlock.lastFOTextProcessed;
  230. prevFOTextThisBlock.nextFOTextThisBlock = this;
  231. } else {
  232. prevFOTextThisBlock = null;
  233. }
  234. }
  235. }
  236. /**
  237. * This method is run as part of endOfNode(), to handle the
  238. * text-transform property for accumulated FOText
  239. */
  240. private void textTransform() {
  241. if (getBuilderContext().inMarker()
  242. || textTransform == Constants.EN_NONE) {
  243. return;
  244. }
  245. charBuffer.rewind();
  246. CharBuffer tmp = charBuffer.slice();
  247. char c;
  248. int lim = charBuffer.limit();
  249. int pos = -1;
  250. while (++pos < lim) {
  251. c = charBuffer.get();
  252. switch (textTransform) {
  253. case Constants.EN_UPPERCASE:
  254. tmp.put(Character.toUpperCase(c));
  255. break;
  256. case Constants.EN_LOWERCASE:
  257. tmp.put(Character.toLowerCase(c));
  258. break;
  259. case Constants.EN_CAPITALIZE:
  260. if (isStartOfWord(pos)) {
  261. /*
  262. Use toTitleCase here. Apparently, some languages use
  263. a different character to represent a letter when using
  264. initial caps than when all of the letters in the word
  265. are capitalized. We will try to let Java handle this.
  266. */
  267. tmp.put(Character.toTitleCase(c));
  268. } else {
  269. tmp.put(c);
  270. }
  271. break;
  272. default:
  273. //should never happen as the property subsystem catches that case
  274. assert false;
  275. //nop
  276. }
  277. }
  278. }
  279. /**
  280. * Determines whether a particular location in an FOText object's text is
  281. * the start of a new "word". The use of "word" here is specifically for
  282. * the text-transform property, but may be useful for other things as
  283. * well, such as word-spacing. The definition of "word" is somewhat ambiguous
  284. * and appears to be definable by the user agent.
  285. *
  286. * @param i index into charBuffer
  287. *
  288. * @return True if the character at this location is the start of a new
  289. * word.
  290. */
  291. private boolean isStartOfWord(int i) {
  292. char prevChar = getRelativeCharInBlock(i, -1);
  293. /* All we are really concerned about here is of what type prevChar
  294. * is. If inputChar is not part of a word, then the Java
  295. * conversions will (we hope) simply return inputChar.
  296. */
  297. switch (isWordChar(prevChar)) {
  298. case IS_WORD_CHAR_TRUE:
  299. return false;
  300. case IS_WORD_CHAR_FALSE:
  301. return true;
  302. /* "MAYBE" implies that additional context is needed. An example is a
  303. * single-quote, either straight or closing, which might be interpreted
  304. * as a possessive or a contraction, or might be a closing quote.
  305. */
  306. case IS_WORD_CHAR_MAYBE:
  307. char prevPrevChar = getRelativeCharInBlock(i, -2);
  308. switch (isWordChar(prevPrevChar)) {
  309. case IS_WORD_CHAR_TRUE:
  310. return false;
  311. case IS_WORD_CHAR_FALSE:
  312. return true;
  313. case IS_WORD_CHAR_MAYBE:
  314. return true;
  315. default:
  316. return false;
  317. }
  318. default:
  319. return false;
  320. }
  321. }
  322. /**
  323. * Finds a character within the current Block that is relative in location
  324. * to a character in the current FOText. Treats all FOText objects within a
  325. * block as one unit, allowing text in adjoining FOText objects to be
  326. * returned if the parameters are outside of the current object.
  327. *
  328. * @param i index into the CharBuffer
  329. * @param offset signed integer with relative position within the
  330. * block of the character to return. To return the character immediately
  331. * preceding i, pass -1. To return the character immediately after i,
  332. * pass 1.
  333. * @return the character in the offset position within the block; \u0000 if
  334. * the offset points to an area outside of the block.
  335. */
  336. private char getRelativeCharInBlock(int i, int offset) {
  337. int charIndex = i + offset;
  338. // The easy case is where the desired character is in the same FOText
  339. if (charIndex >= 0 && charIndex < this.length()) {
  340. return this.charAt(i + offset);
  341. }
  342. // For now, we can't look at following FOText nodes
  343. if (offset > 0) {
  344. return CharUtilities.NULL_CHAR;
  345. }
  346. // Remaining case has the text in some previous FOText node
  347. boolean foundChar = false;
  348. char charToReturn = CharUtilities.NULL_CHAR;
  349. FOText nodeToTest = this;
  350. int remainingOffset = offset + i;
  351. while (!foundChar) {
  352. if (nodeToTest.prevFOTextThisBlock == null) {
  353. break;
  354. }
  355. nodeToTest = nodeToTest.prevFOTextThisBlock;
  356. int diff = nodeToTest.length() + remainingOffset - 1;
  357. if (diff >= 0) {
  358. charToReturn = nodeToTest.charAt(diff);
  359. foundChar = true;
  360. } else {
  361. remainingOffset += diff;
  362. }
  363. }
  364. return charToReturn;
  365. }
  366. /**
  367. * @return The previous FOText node in this Block; null, if this is the
  368. * first FOText in this Block.
  369. */
  370. //public FOText getPrevFOTextThisBlock () {
  371. // return prevFOTextThisBlock;
  372. //}
  373. /**
  374. * @return The next FOText node in this Block; null if this is the last
  375. * FOText in this Block; null if subsequent FOText nodes have not yet been
  376. * processed.
  377. */
  378. //public FOText getNextFOTextThisBlock () {
  379. // return nextFOTextThisBlock;
  380. //}
  381. /**
  382. * @return The nearest ancestor block object which contains this FOText.
  383. */
  384. //public Block getAncestorBlock () {
  385. // return ancestorBlock;
  386. //}
  387. /**
  388. * Determines whether the input char should be considered part of a
  389. * "word". This is used primarily to determine whether the character
  390. * immediately following starts a new word, but may have other uses.
  391. * We have not found a definition of "word" in the standard (1.0), so the
  392. * logic used here is based on the programmer's best guess.
  393. *
  394. * @param inputChar the character to be tested.
  395. * @return int IS_WORD_CHAR_TRUE, IS_WORD_CHAR_FALSE, or IS_WORD_CHAR_MAYBE,
  396. * depending on whether the character should be considered part of a word
  397. * or not.
  398. */
  399. private static int isWordChar(char inputChar) {
  400. switch (Character.getType(inputChar)) {
  401. case Character.COMBINING_SPACING_MARK:
  402. return IS_WORD_CHAR_TRUE;
  403. case Character.CONNECTOR_PUNCTUATION:
  404. return IS_WORD_CHAR_TRUE;
  405. case Character.CONTROL:
  406. return IS_WORD_CHAR_FALSE;
  407. case Character.CURRENCY_SYMBOL:
  408. return IS_WORD_CHAR_TRUE;
  409. case Character.DASH_PUNCTUATION:
  410. if (inputChar == '-') {
  411. return IS_WORD_CHAR_TRUE; //hyphen
  412. }
  413. return IS_WORD_CHAR_FALSE;
  414. case Character.DECIMAL_DIGIT_NUMBER:
  415. return IS_WORD_CHAR_TRUE;
  416. case Character.ENCLOSING_MARK:
  417. return IS_WORD_CHAR_FALSE;
  418. case Character.END_PUNCTUATION:
  419. if (inputChar == '\u2019') {
  420. return IS_WORD_CHAR_MAYBE; //apostrophe, right single quote
  421. }
  422. return IS_WORD_CHAR_FALSE;
  423. case Character.FORMAT:
  424. return IS_WORD_CHAR_FALSE;
  425. case Character.LETTER_NUMBER:
  426. return IS_WORD_CHAR_TRUE;
  427. case Character.LINE_SEPARATOR:
  428. return IS_WORD_CHAR_FALSE;
  429. case Character.LOWERCASE_LETTER:
  430. return IS_WORD_CHAR_TRUE;
  431. case Character.MATH_SYMBOL:
  432. return IS_WORD_CHAR_FALSE;
  433. case Character.MODIFIER_LETTER:
  434. return IS_WORD_CHAR_TRUE;
  435. case Character.MODIFIER_SYMBOL:
  436. return IS_WORD_CHAR_TRUE;
  437. case Character.NON_SPACING_MARK:
  438. return IS_WORD_CHAR_TRUE;
  439. case Character.OTHER_LETTER:
  440. return IS_WORD_CHAR_TRUE;
  441. case Character.OTHER_NUMBER:
  442. return IS_WORD_CHAR_TRUE;
  443. case Character.OTHER_PUNCTUATION:
  444. if (inputChar == '\'') {
  445. return IS_WORD_CHAR_MAYBE; //ASCII apostrophe
  446. }
  447. return IS_WORD_CHAR_FALSE;
  448. case Character.OTHER_SYMBOL:
  449. return IS_WORD_CHAR_TRUE;
  450. case Character.PARAGRAPH_SEPARATOR:
  451. return IS_WORD_CHAR_FALSE;
  452. case Character.PRIVATE_USE:
  453. return IS_WORD_CHAR_FALSE;
  454. case Character.SPACE_SEPARATOR:
  455. return IS_WORD_CHAR_FALSE;
  456. case Character.START_PUNCTUATION:
  457. return IS_WORD_CHAR_FALSE;
  458. case Character.SURROGATE:
  459. return IS_WORD_CHAR_FALSE;
  460. case Character.TITLECASE_LETTER:
  461. return IS_WORD_CHAR_TRUE;
  462. case Character.UNASSIGNED:
  463. return IS_WORD_CHAR_FALSE;
  464. case Character.UPPERCASE_LETTER:
  465. return IS_WORD_CHAR_TRUE;
  466. default:
  467. return IS_WORD_CHAR_FALSE;
  468. }
  469. }
  470. private class TextCharIterator extends CharIterator {
  471. private int currentPosition = 0;
  472. private boolean canRemove = false;
  473. private boolean canReplace = false;
  474. public TextCharIterator() {
  475. }
  476. /** {@inheritDoc} */
  477. public boolean hasNext() {
  478. return (this.currentPosition < charBuffer.limit());
  479. }
  480. /** {@inheritDoc} */
  481. public char nextChar() {
  482. if (this.currentPosition < charBuffer.limit()) {
  483. this.canRemove = true;
  484. this.canReplace = true;
  485. return charBuffer.get(currentPosition++);
  486. } else {
  487. throw new NoSuchElementException();
  488. }
  489. }
  490. /** {@inheritDoc} */
  491. public void remove() {
  492. if (this.canRemove) {
  493. charBuffer.position(currentPosition);
  494. // Slice the buffer at the current position
  495. CharBuffer tmp = charBuffer.slice();
  496. // Reset position to before current character
  497. charBuffer.position(--currentPosition);
  498. if (tmp.hasRemaining()) {
  499. // Transfer any remaining characters
  500. charBuffer.mark();
  501. charBuffer.put(tmp);
  502. charBuffer.reset();
  503. }
  504. // Decrease limit
  505. charBuffer.limit(charBuffer.limit() - 1);
  506. // Make sure following calls fail, unless nextChar() was called
  507. this.canRemove = false;
  508. } else {
  509. throw new IllegalStateException();
  510. }
  511. }
  512. /** {@inheritDoc} */
  513. public void replaceChar(char c) {
  514. if (this.canReplace) {
  515. charBuffer.put(currentPosition - 1, c);
  516. } else {
  517. throw new IllegalStateException();
  518. }
  519. }
  520. }
  521. /**
  522. * @return the Common Font Properties.
  523. */
  524. public CommonFont getCommonFont() {
  525. return commonFont;
  526. }
  527. /**
  528. * @return the Common Hyphenation Properties.
  529. */
  530. public CommonHyphenation getCommonHyphenation() {
  531. return commonHyphenation;
  532. }
  533. /**
  534. * @return the "color" trait.
  535. */
  536. public Color getColor() {
  537. return color;
  538. }
  539. /**
  540. * @return the "keep-together" trait.
  541. */
  542. public KeepProperty getKeepTogether() {
  543. return keepTogether;
  544. }
  545. /**
  546. * @return the "letter-spacing" trait.
  547. */
  548. public Property getLetterSpacing() {
  549. return letterSpacing;
  550. }
  551. /**
  552. * @return the "line-height" trait.
  553. */
  554. public SpaceProperty getLineHeight() {
  555. return lineHeight;
  556. }
  557. /**
  558. * @return the "white-space-treatment" trait
  559. */
  560. public int getWhitespaceTreatment() {
  561. return whiteSpaceTreatment;
  562. }
  563. /**
  564. * @return the "word-spacing" trait.
  565. */
  566. public Property getWordSpacing() {
  567. return wordSpacing;
  568. }
  569. /**
  570. * @return the "wrap-option" trait.
  571. */
  572. public int getWrapOption() {
  573. return wrapOption;
  574. }
  575. /** @return the "text-decoration" trait. */
  576. public CommonTextDecoration getTextDecoration() {
  577. return textDecoration;
  578. }
  579. /** @return the baseline-shift trait */
  580. public Length getBaseLineShift() {
  581. return baselineShift;
  582. }
  583. /** @return the country trait */
  584. public String getCountry() {
  585. return country;
  586. }
  587. /** @return the language trait */
  588. public String getLanguage() {
  589. return language;
  590. }
  591. /** @return the script trait */
  592. public String getScript() {
  593. return script;
  594. }
  595. /** {@inheritDoc} */
  596. public String toString() {
  597. if (charBuffer == null) {
  598. return "";
  599. } else {
  600. CharBuffer cb = charBuffer.duplicate();
  601. cb.rewind();
  602. return cb.toString();
  603. }
  604. }
  605. /** {@inheritDoc} */
  606. public String getLocalName() {
  607. return "#PCDATA";
  608. }
  609. /** {@inheritDoc} */
  610. public String getNormalNamespacePrefix() {
  611. return null;
  612. }
  613. /** {@inheritDoc} */
  614. protected String gatherContextInfo() {
  615. if (this.locator != null) {
  616. return super.gatherContextInfo();
  617. } else {
  618. return this.toString();
  619. }
  620. }
  621. /** {@inheritDoc} */
  622. public char charAt(int position) {
  623. return charBuffer.get(position);
  624. }
  625. /** {@inheritDoc} */
  626. public CharSequence subSequence(int start, int end) {
  627. return charBuffer.subSequence(start, end);
  628. }
  629. /** {@inheritDoc} */
  630. public int length() {
  631. return charBuffer.limit();
  632. }
  633. /**
  634. * Resets the backing <code>java.nio.CharBuffer</code>
  635. */
  636. public void resetBuffer() {
  637. if (charBuffer != null) {
  638. charBuffer.rewind();
  639. }
  640. }
  641. @Override
  642. public boolean isDelimitedTextRangeBoundary(int boundary) {
  643. return false;
  644. }
  645. @Override
  646. public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
  647. this.structureTreeElement = structureTreeElement;
  648. }
  649. @Override
  650. public StructureTreeElement getStructureTreeElement() {
  651. return structureTreeElement;
  652. }
  653. /**
  654. * Set bidirectional level over interval [start,end).
  655. * @param level the resolved level
  656. * @param start the starting index of interval
  657. * @param end the ending index of interval
  658. */
  659. public void setBidiLevel(int level, int start, int end) {
  660. if (start < end) {
  661. if (bidiLevels == null) {
  662. bidiLevels = new int [ length() ];
  663. }
  664. for (int i = start, n = end; i < n; i++) {
  665. bidiLevels [ i ] = level;
  666. }
  667. if (parent != null) {
  668. ((FObj) parent).setBidiLevel(level);
  669. }
  670. } else {
  671. assert start < end;
  672. }
  673. }
  674. /**
  675. * Obtain bidirectional level of each character
  676. * represented by this FOText.
  677. * @return a (possibly empty) array of bidi levels or null
  678. * in case no bidi levels have been assigned
  679. */
  680. public int[] getBidiLevels() {
  681. return bidiLevels;
  682. }
  683. /**
  684. * Obtain bidirectional level of each character over
  685. * interval [start,end).
  686. * @param start the starting index of interval
  687. * @param end the ending index of interval
  688. * @return a (possibly empty) array of bidi levels or null
  689. * in case no bidi levels have been assigned
  690. */
  691. public int[] getBidiLevels(int start, int end) {
  692. if (this.bidiLevels != null) {
  693. assert start <= end;
  694. int n = end - start;
  695. int[] bidiLevels = new int [ n ];
  696. for (int i = 0; i < n; i++) {
  697. bidiLevels[i] = this.bidiLevels [ start + i ];
  698. }
  699. return bidiLevels;
  700. } else {
  701. return null;
  702. }
  703. }
  704. /**
  705. * Obtain bidirectional level of character at
  706. * specified position, which must be a non-negative integer
  707. * less than the length of this FO.
  708. * @param position an offset position into FO's characters
  709. * @return a resolved bidi level or -1 if default
  710. * @throws IndexOutOfBoundsException if position is not non-negative integer
  711. * or is greater than or equal to length
  712. */
  713. public int bidiLevelAt(int position) throws IndexOutOfBoundsException {
  714. if ((position < 0) || (position >= length())) {
  715. throw new IndexOutOfBoundsException();
  716. } else if (bidiLevels != null) {
  717. return bidiLevels [ position ];
  718. } else {
  719. return -1;
  720. }
  721. }
  722. @Override
  723. protected Stack<DelimitedTextRange> collectDelimitedTextRanges(Stack<DelimitedTextRange> ranges,
  724. DelimitedTextRange currentRange) {
  725. if (currentRange != null) {
  726. currentRange.append(charIterator(), this);
  727. }
  728. return ranges;
  729. }
  730. private static class MapRange {
  731. private int start;
  732. private int end;
  733. MapRange(int start, int end) {
  734. this.start = start;
  735. this.end = end;
  736. }
  737. public int hashCode() {
  738. return (start * 31) + end;
  739. }
  740. public boolean equals(Object o) {
  741. if (o instanceof MapRange) {
  742. MapRange r = (MapRange) o;
  743. return (r.start == start) && (r.end == end);
  744. } else {
  745. return false;
  746. }
  747. }
  748. }
  749. }