1 /* 2 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 * 25 */ 26 27 /* 28 * (C) Copyright IBM Corp. 1999, All rights reserved. 29 */ 30 package java.awt.font; 31 32 import java.awt.Font; 33 import java.awt.Toolkit; 34 import java.awt.im.InputMethodHighlight; 35 import java.text.Annotation; 36 import java.text.AttributedCharacterIterator; 37 import java.text.AttributedCharacterIterator.Attribute; 38 import java.util.Vector; 39 import java.util.HashMap; 40 import java.util.Map; 41 import sun.font.CodePointIterator; 42 import sun.font.Decoration; 43 import sun.font.FontResolver; 44 45 /** 46 * This class stores Font, GraphicAttribute, and Decoration intervals 47 * on a paragraph of styled text. 48 * <p> 49 * Currently, this class is optimized for a small number of intervals 50 * (preferrably 1). 51 */ 52 final class StyledParagraph { 53 54 // the length of the paragraph 55 private int length; 56 57 // If there is a single Decoration for the whole paragraph, it 58 // is stored here. Otherwise this field is ignored. 59 60 private Decoration decoration; 61 62 // If there is a single Font or GraphicAttribute for the whole 63 // paragraph, it is stored here. Otherwise this field is ignored. 64 private Object font; 65 66 // If there are multiple Decorations in the paragraph, they are 67 // stored in this Vector, in order. Otherwise this vector and 68 // the decorationStarts array are null. 69 private Vector<Decoration> decorations; 70 // If there are multiple Decorations in the paragraph, 71 // decorationStarts[i] contains the index where decoration i 72 // starts. For convenience, there is an extra entry at the 73 // end of this array with the length of the paragraph. 74 int[] decorationStarts; 75 76 // If there are multiple Fonts/GraphicAttributes in the paragraph, 77 // they are 78 // stored in this Vector, in order. Otherwise this vector and 79 // the fontStarts array are null. 80 private Vector<Object> fonts; 81 // If there are multiple Fonts/GraphicAttributes in the paragraph, 82 // fontStarts[i] contains the index where decoration i 83 // starts. For convenience, there is an extra entry at the 84 // end of this array with the length of the paragraph. 85 int[] fontStarts; 86 87 private static int INITIAL_SIZE = 8; 88 89 /** 90 * Create a new StyledParagraph over the given styled text. 91 * @param aci an iterator over the text 92 * @param chars the characters extracted from aci 93 */ 94 public StyledParagraph(AttributedCharacterIterator aci, 95 char[] chars) { 96 97 int start = aci.getBeginIndex(); 98 int end = aci.getEndIndex(); 99 length = end - start; 100 101 int index = start; 102 aci.first(); 103 104 do { 105 final int nextRunStart = aci.getRunLimit(); 106 final int localIndex = index-start; 107 108 Map<? extends Attribute, ?> attributes = aci.getAttributes(); 109 attributes = addInputMethodAttrs(attributes); 110 Decoration d = Decoration.getDecoration(attributes); 111 addDecoration(d, localIndex); 112 113 Object f = getGraphicOrFont(attributes); 114 if (f == null) { 115 addFonts(chars, attributes, localIndex, nextRunStart-start); 116 } 117 else { 118 addFont(f, localIndex); 119 } 120 121 aci.setIndex(nextRunStart); 122 index = nextRunStart; 123 124 } while (index < end); 125 126 // Add extra entries to starts arrays with the length 127 // of the paragraph. 'this' is used as a dummy value 128 // in the Vector. 129 if (decorations != null) { 130 decorationStarts = addToVector(this, length, decorations, decorationStarts); 131 } 132 if (fonts != null) { 133 fontStarts = addToVector(this, length, fonts, fontStarts); 134 } 135 } 136 137 /** 138 * Adjust indices in starts to reflect an insertion after pos. 139 * Any index in starts greater than pos will be increased by 1. 140 */ 141 private static void insertInto(int pos, int[] starts, int numStarts) { 142 143 while (starts[--numStarts] > pos) { 144 starts[numStarts] += 1; 145 } 146 } 147 148 /** 149 * Return a StyledParagraph reflecting the insertion of a single character 150 * into the text. This method will attempt to reuse the given paragraph, 151 * but may create a new paragraph. 152 * @param aci an iterator over the text. The text should be the same as the 153 * text used to create (or most recently update) oldParagraph, with 154 * the exception of inserting a single character at insertPos. 155 * @param chars the characters in aci 156 * @param insertPos the index of the new character in aci 157 * @param oldParagraph a StyledParagraph for the text in aci before the 158 * insertion 159 */ 160 public static StyledParagraph insertChar(AttributedCharacterIterator aci, 161 char[] chars, 162 int insertPos, 163 StyledParagraph oldParagraph) { 164 165 // If the styles at insertPos match those at insertPos-1, 166 // oldParagraph will be reused. Otherwise we create a new 167 // paragraph. 168 169 char ch = aci.setIndex(insertPos); 170 int relativePos = Math.max(insertPos - aci.getBeginIndex() - 1, 0); 171 172 Map<? extends Attribute, ?> attributes = 173 addInputMethodAttrs(aci.getAttributes()); 174 Decoration d = Decoration.getDecoration(attributes); 175 if (!oldParagraph.getDecorationAt(relativePos).equals(d)) { 176 return new StyledParagraph(aci, chars); 177 } 178 Object f = getGraphicOrFont(attributes); 179 if (f == null) { 180 FontResolver resolver = FontResolver.getInstance(); 181 int fontIndex = resolver.getFontIndex(ch); 182 f = resolver.getFont(fontIndex, attributes); 183 } 184 if (!oldParagraph.getFontOrGraphicAt(relativePos).equals(f)) { 185 return new StyledParagraph(aci, chars); 186 } 187 188 // insert into existing paragraph 189 oldParagraph.length += 1; 190 if (oldParagraph.decorations != null) { 191 insertInto(relativePos, 192 oldParagraph.decorationStarts, 193 oldParagraph.decorations.size()); 194 } 195 if (oldParagraph.fonts != null) { 196 insertInto(relativePos, 197 oldParagraph.fontStarts, 198 oldParagraph.fonts.size()); 199 } 200 return oldParagraph; 201 } 202 203 /** 204 * Adjust indices in starts to reflect a deletion after deleteAt. 205 * Any index in starts greater than deleteAt will be increased by 1. 206 * It is the caller's responsibility to make sure that no 0-length 207 * runs result. 208 */ 209 private static void deleteFrom(int deleteAt, int[] starts, int numStarts) { 210 211 while (starts[--numStarts] > deleteAt) { 212 starts[numStarts] -= 1; 213 } 214 } 215 216 /** 217 * Return a StyledParagraph reflecting the insertion of a single character 218 * into the text. This method will attempt to reuse the given paragraph, 219 * but may create a new paragraph. 220 * @param aci an iterator over the text. The text should be the same as the 221 * text used to create (or most recently update) oldParagraph, with 222 * the exception of deleting a single character at deletePos. 223 * @param chars the characters in aci 224 * @param deletePos the index where a character was removed 225 * @param oldParagraph a StyledParagraph for the text in aci before the 226 * insertion 227 */ 228 public static StyledParagraph deleteChar(AttributedCharacterIterator aci, 229 char[] chars, 230 int deletePos, 231 StyledParagraph oldParagraph) { 232 233 // We will reuse oldParagraph unless there was a length-1 run 234 // at deletePos. We could do more work and check the individual 235 // Font and Decoration runs, but we don't right now... 236 deletePos -= aci.getBeginIndex(); 237 238 if (oldParagraph.decorations == null && oldParagraph.fonts == null) { 239 oldParagraph.length -= 1; 240 return oldParagraph; 241 } 242 243 if (oldParagraph.getRunLimit(deletePos) == deletePos+1) { 244 if (deletePos == 0 || oldParagraph.getRunLimit(deletePos-1) == deletePos) { 245 return new StyledParagraph(aci, chars); 246 } 247 } 248 249 oldParagraph.length -= 1; 250 if (oldParagraph.decorations != null) { 251 deleteFrom(deletePos, 252 oldParagraph.decorationStarts, 253 oldParagraph.decorations.size()); 254 } 255 if (oldParagraph.fonts != null) { 256 deleteFrom(deletePos, 257 oldParagraph.fontStarts, 258 oldParagraph.fonts.size()); 259 } 260 return oldParagraph; 261 } 262 263 /** 264 * Return the index at which there is a different Font, GraphicAttribute, or 265 * Dcoration than at the given index. 266 * @param index a valid index in the paragraph 267 * @return the first index where there is a change in attributes from 268 * those at index 269 */ 270 public int getRunLimit(int index) { 271 272 if (index < 0 || index >= length) { 273 throw new IllegalArgumentException("index out of range"); 274 } 275 int limit1 = length; 276 if (decorations != null) { 277 int run = findRunContaining(index, decorationStarts); 278 limit1 = decorationStarts[run+1]; 279 } 280 int limit2 = length; 281 if (fonts != null) { 282 int run = findRunContaining(index, fontStarts); 283 limit2 = fontStarts[run+1]; 284 } 285 return Math.min(limit1, limit2); 286 } 287 288 /** 289 * Return the Decoration in effect at the given index. 290 * @param index a valid index in the paragraph 291 * @return the Decoration at index. 292 */ 293 public Decoration getDecorationAt(int index) { 294 295 if (index < 0 || index >= length) { 296 throw new IllegalArgumentException("index out of range"); 297 } 298 if (decorations == null) { 299 return decoration; 300 } 301 int run = findRunContaining(index, decorationStarts); 302 return decorations.elementAt(run); 303 } 304 305 /** 306 * Return the Font or GraphicAttribute in effect at the given index. 307 * The client must test the type of the return value to determine what 308 * it is. 309 * @param index a valid index in the paragraph 310 * @return the Font or GraphicAttribute at index. 311 */ 312 public Object getFontOrGraphicAt(int index) { 313 314 if (index < 0 || index >= length) { 315 throw new IllegalArgumentException("index out of range"); 316 } 317 if (fonts == null) { 318 return font; 319 } 320 int run = findRunContaining(index, fontStarts); 321 return fonts.elementAt(run); 322 } 323 324 /** 325 * Return i such that starts[i] <= index < starts[i+1]. starts 326 * must be in increasing order, with at least one element greater 327 * than index. 328 */ 329 private static int findRunContaining(int index, int[] starts) { 330 331 for (int i=1; true; i++) { 332 if (starts[i] > index) { 333 return i-1; 334 } 335 } 336 } 337 338 /** 339 * Append the given Object to the given Vector. Add 340 * the given index to the given starts array. If the 341 * starts array does not have room for the index, a 342 * new array is created and returned. 343 */ 344 @SuppressWarnings({"rawtypes", "unchecked"}) 345 private static int[] addToVector(Object obj, 346 int index, 347 Vector v, 348 int[] starts) { 349 350 if (!v.lastElement().equals(obj)) { 351 v.addElement(obj); 352 int count = v.size(); 353 if (starts.length == count) { 354 int[] temp = new int[starts.length*2]; 355 System.arraycopy(starts, 0, temp, 0, starts.length); 356 starts = temp; 357 } 358 starts[count-1] = index; 359 } 360 return starts; 361 } 362 363 /** 364 * Add a new Decoration run with the given Decoration at the 365 * given index. 366 */ 367 private void addDecoration(Decoration d, int index) { 368 369 if (decorations != null) { 370 decorationStarts = addToVector(d, 371 index, 372 decorations, 373 decorationStarts); 374 } 375 else if (decoration == null) { 376 decoration = d; 377 } 378 else { 379 if (!decoration.equals(d)) { 380 decorations = new Vector<Decoration>(INITIAL_SIZE); 381 decorations.addElement(decoration); 382 decorations.addElement(d); 383 decorationStarts = new int[INITIAL_SIZE]; 384 decorationStarts[0] = 0; 385 decorationStarts[1] = index; 386 } 387 } 388 } 389 390 /** 391 * Add a new Font/GraphicAttribute run with the given object at the 392 * given index. 393 */ 394 private void addFont(Object f, int index) { 395 396 if (fonts != null) { 397 fontStarts = addToVector(f, index, fonts, fontStarts); 398 } 399 else if (font == null) { 400 font = f; 401 } 402 else { 403 if (!font.equals(f)) { 404 fonts = new Vector<Object>(INITIAL_SIZE); 405 fonts.addElement(font); 406 fonts.addElement(f); 407 fontStarts = new int[INITIAL_SIZE]; 408 fontStarts[0] = 0; 409 fontStarts[1] = index; 410 } 411 } 412 } 413 414 /** 415 * Resolve the given chars into Fonts using FontResolver, then add 416 * font runs for each. 417 */ 418 private void addFonts(char[] chars, Map<? extends Attribute, ?> attributes, 419 int start, int limit) { 420 421 FontResolver resolver = FontResolver.getInstance(); 422 CodePointIterator iter = CodePointIterator.create(chars, start, limit); 423 for (int runStart = iter.charIndex(); runStart < limit; runStart = iter.charIndex()) { 424 int fontIndex = resolver.nextFontRunIndex(iter); 425 addFont(resolver.getFont(fontIndex, attributes), runStart); 426 } 427 } 428 429 /** 430 * Return a Map with entries from oldStyles, as well as input 431 * method entries, if any. 432 */ 433 static Map<? extends Attribute, ?> 434 addInputMethodAttrs(Map<? extends Attribute, ?> oldStyles) { 435 436 Object value = oldStyles.get(TextAttribute.INPUT_METHOD_HIGHLIGHT); 437 438 try { 439 if (value != null) { 440 if (value instanceof Annotation) { 441 value = ((Annotation)value).getValue(); 442 } 443 444 InputMethodHighlight hl; 445 hl = (InputMethodHighlight) value; 446 447 Map<? extends Attribute, ?> imStyles = null; 448 try { 449 imStyles = hl.getStyle(); 450 } catch (NoSuchMethodError e) { 451 } 452 453 if (imStyles == null) { 454 Toolkit tk = Toolkit.getDefaultToolkit(); 455 imStyles = tk.mapInputMethodHighlight(hl); 456 } 457 458 if (imStyles != null) { 459 HashMap<Attribute, Object> 460 newStyles = new HashMap<>(5, (float)0.9); 461 newStyles.putAll(oldStyles); 462 463 newStyles.putAll(imStyles); 464 465 return newStyles; 466 } 467 } 468 } 469 catch(ClassCastException e) { 470 } 471 472 return oldStyles; 473 } 474 475 /** 476 * Extract a GraphicAttribute or Font from the given attributes. 477 * If attributes does not contain a GraphicAttribute, Font, or 478 * Font family entry this method returns null. 479 */ 480 private static Object getGraphicOrFont( 481 Map<? extends Attribute, ?> attributes) { 482 483 Object value = attributes.get(TextAttribute.CHAR_REPLACEMENT); 484 if (value != null) { 485 return value; 486 } 487 value = attributes.get(TextAttribute.FONT); 488 if (value != null) { 489 return value; 490 } 491 492 if (attributes.get(TextAttribute.FAMILY) != null) { 493 return Font.getFont(attributes); 494 } 495 else { 496 return null; 497 } 498 } 499 }