1. /*
  2. * @(#)StyledParagraph.java 1.4 99/12/14
  3. * (C) Copyright IBM Corp. 1999, All rights reserved.
  4. */
  5. package java.awt.font;
  6. import java.text.AttributedCharacterIterator;
  7. import java.text.Annotation;
  8. import java.awt.Font;
  9. import java.awt.Toolkit;
  10. import sun.awt.font.Decoration;
  11. import java.util.Vector;
  12. import java.util.Hashtable;
  13. import java.util.Map;
  14. import java.text.AttributedCharacterIterator;
  15. import sun.awt.font.FontResolver;
  16. import java.awt.im.InputMethodHighlight;
  17. /**
  18. * This class stores Font, GraphicAttribute, and Decoration intervals
  19. * on a paragraph of styled text.
  20. * <p>
  21. * Currently, this class is optimized for a small number of intervals
  22. * (preferrably 1).
  23. */
  24. final class StyledParagraph {
  25. // the length of the paragraph
  26. private int length;
  27. // If there is a single Decoration for the whole paragraph, it
  28. // is stored here. Otherwise this field is ignored.
  29. private Decoration decoration;
  30. // If there is a single Font or GraphicAttribute for the whole
  31. // paragraph, it is stored here. Otherwise this field is ignored.
  32. private Object font;
  33. // If there are multiple Decorations in the paragraph, they are
  34. // stored in this Vector, in order. Otherwise this vector and
  35. // the decorationStarts array are null.
  36. private Vector decorations;
  37. // If there are multiple Decorations in the paragraph,
  38. // decorationStarts[i] contains the index where decoration i
  39. // starts. For convenience, there is an extra entry at the
  40. // end of this array with the length of the paragraph.
  41. int[] decorationStarts;
  42. // If there are multiple Fonts/GraphicAttributes in the paragraph,
  43. // they are
  44. // stored in this Vector, in order. Otherwise this vector and
  45. // the fontStarts array are null.
  46. private Vector fonts;
  47. // If there are multiple Fonts/GraphicAttributes in the paragraph,
  48. // fontStarts[i] contains the index where decoration i
  49. // starts. For convenience, there is an extra entry at the
  50. // end of this array with the length of the paragraph.
  51. int[] fontStarts;
  52. private static int INITIAL_SIZE = 8;
  53. /**
  54. * Create a new StyledParagraph over the given styled text.
  55. * @param aci an iterator over the text
  56. * @param chars the characters extracted from aci
  57. */
  58. public StyledParagraph(AttributedCharacterIterator aci,
  59. char[] chars) {
  60. int start = aci.getBeginIndex();
  61. int end = aci.getEndIndex();
  62. length = end - start;
  63. int index = start;
  64. aci.first();
  65. do {
  66. final int nextRunStart = aci.getRunLimit();
  67. final int localIndex = index-start;
  68. Map attributes = aci.getAttributes();
  69. attributes = addInputMethodAttrs(attributes);
  70. Decoration d = Decoration.getDecoration(attributes);
  71. addDecoration(d, localIndex);
  72. Object f = getGraphicOrFont(attributes);
  73. if (f == null) {
  74. addFonts(chars, attributes, localIndex, nextRunStart-start);
  75. }
  76. else {
  77. addFont(f, localIndex);
  78. }
  79. aci.setIndex(nextRunStart);
  80. index = nextRunStart;
  81. } while (index < end);
  82. // Add extra entries to starts arrays with the length
  83. // of the paragraph. 'this' is used as a dummy value
  84. // in the Vector.
  85. if (decorations != null) {
  86. decorationStarts = addToVector(this, length, decorations, decorationStarts);
  87. }
  88. if (fonts != null) {
  89. fontStarts = addToVector(this, length, fonts, fontStarts);
  90. }
  91. }
  92. /**
  93. * Adjust indices in starts to reflect an insertion after pos.
  94. * Any index in starts greater than pos will be increased by 1.
  95. */
  96. private static void insertInto(int pos, int[] starts, int numStarts) {
  97. while (starts[--numStarts] > pos) {
  98. starts[numStarts] += 1;
  99. }
  100. }
  101. /**
  102. * Return a StyledParagraph reflecting the insertion of a single character
  103. * into the text. This method will attempt to reuse the given paragraph,
  104. * but may create a new paragraph.
  105. * @param aci an iterator over the text. The text should be the same as the
  106. * text used to create (or most recently update) oldParagraph, with
  107. * the exception of inserting a single character at insertPos.
  108. * @param chars the characters in aci
  109. * @param insertPos the index of the new character in aci
  110. * @param oldParagraph a StyledParagraph for the text in aci before the
  111. * insertion
  112. */
  113. public static StyledParagraph insertChar(AttributedCharacterIterator aci,
  114. char[] chars,
  115. int insertPos,
  116. StyledParagraph oldParagraph) {
  117. // If the styles at insertPos match those at insertPos-1,
  118. // oldParagraph will be reused. Otherwise we create a new
  119. // paragraph.
  120. char ch = aci.setIndex(insertPos);
  121. int relativePos = Math.max(insertPos - aci.getBeginIndex() - 1, 0);
  122. Map attributes = addInputMethodAttrs(aci.getAttributes());
  123. Decoration d = Decoration.getDecoration(attributes);
  124. if (!oldParagraph.getDecorationAt(relativePos).equals(d)) {
  125. return new StyledParagraph(aci, chars);
  126. }
  127. Object f = getGraphicOrFont(attributes);
  128. if (f == null) {
  129. FontResolver resolver = FontResolver.getInstance();
  130. int fontIndex = resolver.getFontIndex(ch);
  131. f = resolver.getFont(fontIndex, attributes);
  132. }
  133. if (!oldParagraph.getFontOrGraphicAt(relativePos).equals(f)) {
  134. return new StyledParagraph(aci, chars);
  135. }
  136. // insert into existing paragraph
  137. oldParagraph.length += 1;
  138. if (oldParagraph.decorations != null) {
  139. insertInto(relativePos,
  140. oldParagraph.decorationStarts,
  141. oldParagraph.decorations.size());
  142. }
  143. if (oldParagraph.fonts != null) {
  144. insertInto(relativePos,
  145. oldParagraph.fontStarts,
  146. oldParagraph.fonts.size());
  147. }
  148. return oldParagraph;
  149. }
  150. /**
  151. * Adjust indices in starts to reflect a deletion after deleteAt.
  152. * Any index in starts greater than deleteAt will be increased by 1.
  153. * It is the caller's responsibility to make sure that no 0-length
  154. * runs result.
  155. */
  156. private static void deleteFrom(int deleteAt, int[] starts, int numStarts) {
  157. while (starts[--numStarts] > deleteAt) {
  158. starts[numStarts] -= 1;
  159. }
  160. }
  161. /**
  162. * Return a StyledParagraph reflecting the insertion of a single character
  163. * into the text. This method will attempt to reuse the given paragraph,
  164. * but may create a new paragraph.
  165. * @param aci an iterator over the text. The text should be the same as the
  166. * text used to create (or most recently update) oldParagraph, with
  167. * the exception of deleting a single character at deletePos.
  168. * @param chars the characters in aci
  169. * @param deletePos the index where a character was removed
  170. * @param oldParagraph a StyledParagraph for the text in aci before the
  171. * insertion
  172. */
  173. public static StyledParagraph deleteChar(AttributedCharacterIterator aci,
  174. char[] chars,
  175. int deletePos,
  176. StyledParagraph oldParagraph) {
  177. // We will reuse oldParagraph unless there was a length-1 run
  178. // at deletePos. We could do more work and check the individual
  179. // Font and Decoration runs, but we don't right now...
  180. deletePos -= aci.getBeginIndex();
  181. if (oldParagraph.decorations == null && oldParagraph.fonts == null) {
  182. oldParagraph.length -= 1;
  183. return oldParagraph;
  184. }
  185. if (oldParagraph.getRunLimit(deletePos) == deletePos+1) {
  186. if (deletePos == 0 || oldParagraph.getRunLimit(deletePos-1) == deletePos) {
  187. return new StyledParagraph(aci, chars);
  188. }
  189. }
  190. oldParagraph.length -= 1;
  191. if (oldParagraph.decorations != null) {
  192. deleteFrom(deletePos,
  193. oldParagraph.decorationStarts,
  194. oldParagraph.decorations.size());
  195. }
  196. if (oldParagraph.fonts != null) {
  197. deleteFrom(deletePos,
  198. oldParagraph.fontStarts,
  199. oldParagraph.fonts.size());
  200. }
  201. return oldParagraph;
  202. }
  203. /**
  204. * Return the index at which there is a different Font, GraphicAttribute, or
  205. * Dcoration than at the given index.
  206. * @param index a valid index in the paragraph
  207. * @return the first index where there is a change in attributes from
  208. * those at index
  209. */
  210. public int getRunLimit(int index) {
  211. if (index < 0 || index >= length) {
  212. throw new IllegalArgumentException("index out of range");
  213. }
  214. int limit1 = length;
  215. if (decorations != null) {
  216. int run = findRunContaining(index, decorationStarts);
  217. limit1 = decorationStarts[run+1];
  218. }
  219. int limit2 = length;
  220. if (fonts != null) {
  221. int run = findRunContaining(index, fontStarts);
  222. limit2 = fontStarts[run+1];
  223. }
  224. return Math.min(limit1, limit2);
  225. }
  226. /**
  227. * Return the Decoration in effect at the given index.
  228. * @param index a valid index in the paragraph
  229. * @return the Decoration at index.
  230. */
  231. public Decoration getDecorationAt(int index) {
  232. if (index < 0 || index >= length) {
  233. throw new IllegalArgumentException("index out of range");
  234. }
  235. if (decorations == null) {
  236. return decoration;
  237. }
  238. int run = findRunContaining(index, decorationStarts);
  239. return (Decoration) decorations.elementAt(run);
  240. }
  241. /**
  242. * Return the Font or GraphicAttribute in effect at the given index.
  243. * The client must test the type of the return value to determine what
  244. * it is.
  245. * @param index a valid index in the paragraph
  246. * @return the Font or GraphicAttribute at index.
  247. */
  248. public Object getFontOrGraphicAt(int index) {
  249. if (index < 0 || index >= length) {
  250. throw new IllegalArgumentException("index out of range");
  251. }
  252. if (fonts == null) {
  253. return font;
  254. }
  255. int run = findRunContaining(index, fontStarts);
  256. return fonts.elementAt(run);
  257. }
  258. /**
  259. * Return i such that starts[i] <= index < starts[i+1]. starts
  260. * must be in increasing order, with at least one element greater
  261. * than index.
  262. */
  263. private static int findRunContaining(int index, int[] starts) {
  264. for (int i=1; true; i++) {
  265. if (starts[i] > index) {
  266. return i-1;
  267. }
  268. }
  269. }
  270. /**
  271. * Append the given Object to the given Vector. Add
  272. * the given index to the given starts array. If the
  273. * starts array does not have room for the index, a
  274. * new array is created and returned.
  275. */
  276. private static int[] addToVector(Object obj,
  277. int index,
  278. Vector v,
  279. int[] starts) {
  280. if (!v.lastElement().equals(obj)) {
  281. v.addElement(obj);
  282. int count = v.size();
  283. if (starts.length == count) {
  284. int[] temp = new int[starts.length*2];
  285. System.arraycopy(starts, 0, temp, 0, starts.length);
  286. starts = temp;
  287. }
  288. starts[count-1] = index;
  289. }
  290. return starts;
  291. }
  292. /**
  293. * Add a new Decoration run with the given Decoration at the
  294. * given index.
  295. */
  296. private void addDecoration(Decoration d, int index) {
  297. if (decorations != null) {
  298. decorationStarts = addToVector(d,
  299. index,
  300. decorations,
  301. decorationStarts);
  302. }
  303. else if (decoration == null) {
  304. decoration = d;
  305. }
  306. else {
  307. if (!decoration.equals(d)) {
  308. decorations = new Vector(INITIAL_SIZE);
  309. decorations.addElement(decoration);
  310. decorations.addElement(d);
  311. decorationStarts = new int[INITIAL_SIZE];
  312. decorationStarts[0] = 0;
  313. decorationStarts[1] = index;
  314. }
  315. }
  316. }
  317. /**
  318. * Add a new Font/GraphicAttribute run with the given object at the
  319. * given index.
  320. */
  321. private void addFont(Object f, int index) {
  322. if (fonts != null) {
  323. fontStarts = addToVector(f, index, fonts, fontStarts);
  324. }
  325. else if (font == null) {
  326. font = f;
  327. }
  328. else {
  329. if (!font.equals(f)) {
  330. fonts = new Vector(INITIAL_SIZE);
  331. fonts.addElement(font);
  332. fonts.addElement(f);
  333. fontStarts = new int[INITIAL_SIZE];
  334. fontStarts[0] = 0;
  335. fontStarts[1] = index;
  336. }
  337. }
  338. }
  339. /**
  340. * Resolve the given chars into Fonts using FontResolver, then add
  341. * font runs for each.
  342. */
  343. private void addFonts(char[] chars, Map attributes, int start, int limit) {
  344. FontResolver resolver = FontResolver.getInstance();
  345. do {
  346. int runStart = start;
  347. int fontIndex = resolver.getFontIndex(chars[start]);
  348. for (start++; start < limit; start++) {
  349. if (resolver.getFontIndex(chars[start]) != fontIndex) {
  350. break;
  351. }
  352. }
  353. addFont(resolver.getFont(fontIndex, attributes), runStart);
  354. } while (start < limit);
  355. }
  356. /**
  357. * Return a Map with entries from oldStyles, as well as input
  358. * method entries, if any.
  359. */
  360. static Map addInputMethodAttrs(Map oldStyles) {
  361. Object value = oldStyles.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
  362. try {
  363. if (value != null) {
  364. if (value instanceof Annotation) {
  365. value = ((Annotation)value).getValue();
  366. }
  367. InputMethodHighlight hl;
  368. hl = (InputMethodHighlight) value;
  369. Map imStyles = null;
  370. try {
  371. imStyles = hl.getStyle();
  372. } catch (NoSuchMethodError e) {
  373. }
  374. if (imStyles == null) {
  375. Toolkit tk = Toolkit.getDefaultToolkit();
  376. imStyles = tk.mapInputMethodHighlight(hl);
  377. }
  378. if (imStyles != null) {
  379. Hashtable newStyles = new Hashtable(5, (float)0.9);
  380. newStyles.putAll(oldStyles);
  381. newStyles.putAll(imStyles);
  382. return newStyles;
  383. }
  384. }
  385. }
  386. catch(ClassCastException e) {
  387. }
  388. return oldStyles;
  389. }
  390. /**
  391. * Extract a GraphicAttribute or Font from the given attributes.
  392. * If attributes does not contain a GraphicAttribute, Font, or
  393. * Font family entry this method returns null.
  394. */
  395. private static Object getGraphicOrFont(Map attributes) {
  396. Object value = attributes.get(TextAttribute.CHAR_REPLACEMENT);
  397. if (value != null) {
  398. return value;
  399. }
  400. value = attributes.get(TextAttribute.FONT);
  401. if (value != null) {
  402. return value;
  403. }
  404. if (attributes.get(TextAttribute.FAMILY) != null) {
  405. return Font.getFont(attributes);
  406. }
  407. else {
  408. return null;
  409. }
  410. }
  411. }