1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 package org.openoffice.java.accessibility; 24 25 import com.sun.star.accessibility.*; 26 import com.sun.star.awt.*; 27 import com.sun.star.style.*; 28 import com.sun.star.uno.*; 29 30 import org.openoffice.java.accessibility.logging.*; 31 32 import java.text.BreakIterator; 33 import java.util.Locale; 34 35 import javax.accessibility.AccessibleContext; 36 import javax.accessibility.AccessibleText; 37 38 import javax.swing.text.StyleConstants; 39 40 /** The GenericAccessibleEditableText mapps the calls to the java AccessibleEditableText 41 * interface to the corresponding methods of the UNO XAccessibleEditableText interface. 42 */ 43 public class AccessibleTextImpl implements javax.accessibility.AccessibleText { 44 final static double toPointFactor = 1 / ((7 / 10) + 34.5); 45 final static String[] attributeList = { 46 "ParaAdjust", "CharBackColor", "CharWeight", "ParaFirstLineIndent", 47 "CharFontPitch", "CharHeight", "CharColor", "CharPosture", 48 "ParaLeftMargin", "ParaLineSpacing", "ParaTopMargin", "ParaBottomMargin", 49 "CharStrikeout", "CharEscapement", "ParaTabStops", "CharUnderline" 50 }; 51 52 final static String[] localeAttributeList = { 53 "CharLocale", "CharLocaleAsian", "CharLocaleComplex" 54 }; 55 56 XAccessibleText unoObject; 57 private javax.swing.text.TabSet tabSet = null; 58 private javax.swing.text.TabStop[] tabStops = null; 59 private static Type TextSegmentType = new Type(TextSegment.class); 60 private static Type UnoLocaleType = new Type(com.sun.star.lang.Locale.class); 61 62 /** Creates new GenericAccessibleEditableText object */ AccessibleTextImpl(XAccessibleText xAccessibleText)63 public AccessibleTextImpl(XAccessibleText xAccessibleText) { 64 65 if (Build.PRODUCT) { 66 unoObject = xAccessibleText; 67 } else { 68 String property = System.getProperty("AccessBridgeLogging"); 69 if ((property != null) && (property.indexOf("text") != -1)) { 70 unoObject = new XAccessibleTextLog(xAccessibleText); 71 } else { 72 unoObject = xAccessibleText; 73 } 74 } 75 } 76 AccessibleTextImpl()77 public AccessibleTextImpl() { 78 } 79 get(com.sun.star.uno.XInterface unoObject)80 public static javax.accessibility.AccessibleText get(com.sun.star.uno.XInterface unoObject) { 81 try { 82 XAccessibleText unoAccessibleText = (XAccessibleText) 83 UnoRuntime.queryInterface(XAccessibleText.class, unoObject); 84 if (unoAccessibleText != null) { 85 return new AccessibleTextImpl(unoAccessibleText); 86 } 87 } catch (com.sun.star.uno.RuntimeException e) { 88 } 89 return null; 90 } 91 convertTextSegment(Object any)92 protected static Object convertTextSegment(Object any) { 93 try { 94 if (AnyConverter.isObject(any)) { 95 TextSegment ts = (TextSegment) 96 AnyConverter.toObject(TextSegmentType, any); 97 if (ts != null) { 98 // Since there is nothing like a "range" object in the JAA yet, 99 // the Object[3] is a private negotiation with the JABG 100 Object[] array = { new Integer(ts.SegmentStart), 101 new Integer(ts.SegmentEnd), ts.SegmentText }; 102 return array; 103 } 104 } 105 } catch (com.sun.star.lang.IllegalArgumentException e) { 106 } 107 108 return null; 109 } 110 111 /** Returns the locale object. 112 * 113 * Since switching the UI language only takes effect on the next 114 * office start, UI elements can return a cached value here - given 115 * that Java UNO initializes the default locale correctly, this is 116 * the perfect place to grab this cached values. 117 * 118 * However, since there are more sophisticated components with 119 * potentially more than one locale, we first check for the 120 * CharLocale[Asian|Complex] property. 121 */ 122 getLocale(int index)123 protected java.util.Locale getLocale(int index) { 124 try { 125 com.sun.star.beans.PropertyValue[] propertyValues = 126 unoObject.getCharacterAttributes(index, localeAttributeList); 127 128 if (null != propertyValues) { 129 for (int i = 0; i < propertyValues.length; i++) { 130 com.sun.star.lang.Locale unoLocale = (com.sun.star.lang.Locale) 131 AnyConverter.toObject(UnoLocaleType, propertyValues[i]); 132 if (unoLocale != null) { 133 return new java.util.Locale(unoLocale.Language, unoLocale.Country); 134 } 135 } 136 } 137 138 return java.util.Locale.getDefault(); 139 } catch (com.sun.star.lang.IllegalArgumentException e) { 140 return java.util.Locale.getDefault(); 141 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 142 return java.util.Locale.getDefault(); 143 } 144 } 145 146 147 /** Returns the string after a given index 148 * 149 * The Java word iterator has a different understanding of what 150 * a word is than the word iterator used by OOo, so we use the 151 * Java iterators to ensure maximal compatibility with Java. 152 */ getAfterIndex(int part, int index)153 public String getAfterIndex(int part, int index) { 154 switch (part) { 155 case AccessibleText.CHARACTER: 156 try { 157 String s = unoObject.getText(); 158 return s.substring(index+1, index+2); 159 } catch (IndexOutOfBoundsException e) { 160 return null; 161 } 162 case AccessibleText.WORD: 163 try { 164 String s = unoObject.getText(); 165 BreakIterator words = BreakIterator.getWordInstance(getLocale(index)); 166 words.setText(s); 167 int start = words.following(index); 168 if (start == BreakIterator.DONE || start >= s.length()) { 169 return null; 170 } 171 int end = words.following(start); 172 if (end == BreakIterator.DONE || end >= s.length()) { 173 return null; 174 } 175 return s.substring(start, end); 176 } catch (IllegalArgumentException e) { 177 return null; 178 } catch (IndexOutOfBoundsException e) { 179 return null; 180 } 181 case AccessibleText.SENTENCE: 182 try { 183 String s = unoObject.getText(); 184 BreakIterator sentence = 185 BreakIterator.getSentenceInstance(getLocale(index)); 186 sentence.setText(s); 187 int start = sentence.following(index); 188 if (start == BreakIterator.DONE || start >= s.length()) { 189 return null; 190 } 191 int end = sentence.following(start); 192 if (end == BreakIterator.DONE || end >= s.length()) { 193 return null; 194 } 195 return s.substring(start, end); 196 } catch (IllegalArgumentException e) { 197 return null; 198 } catch (IndexOutOfBoundsException e) { 199 return null; 200 } 201 case 4: 202 try { 203 TextSegment ts = unoObject.getTextBehindIndex(index, AccessibleTextType.LINE); 204 return ts.SegmentText; 205 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 206 // Workaround for #104847# 207 if (index > 0 && getCharCount() == index) { 208 return getAfterIndex(part, index - 1); 209 } 210 return null; 211 } catch (com.sun.star.lang.IllegalArgumentException e) { 212 return null; 213 } 214 case 5: 215 try { 216 TextSegment ts = unoObject.getTextBehindIndex(index, AccessibleTextType.ATTRIBUTE_RUN); 217 return ts.SegmentText; 218 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 219 return null; 220 } catch (com.sun.star.lang.IllegalArgumentException e) { 221 return null; 222 } 223 default: 224 return null; 225 } 226 } 227 228 /** Returns the zero-based offset of the caret */ getCaretPosition()229 public int getCaretPosition() { 230 try { 231 return unoObject.getCaretPosition(); 232 } catch (com.sun.star.uno.RuntimeException e) { 233 return -1; 234 } 235 } 236 237 /** Returns the start offset within the selected text */ getSelectionStart()238 public int getSelectionStart() { 239 try { 240 int index = unoObject.getSelectionStart(); 241 242 if (index == -1) { 243 index = getCaretPosition(); 244 } 245 246 return index; 247 } catch (com.sun.star.uno.RuntimeException e) { 248 return -1; 249 } 250 } 251 setAttribute(javax.swing.text.MutableAttributeSet as, com.sun.star.beans.PropertyValue property)252 protected void setAttribute(javax.swing.text.MutableAttributeSet as, 253 com.sun.star.beans.PropertyValue property) { 254 try { 255 // Map alignment attribute 256 if (property.Name.equals("ParaAdjust")) { 257 ParagraphAdjust adjust = null; 258 259 if (property.Value instanceof ParagraphAdjust) { 260 adjust = (ParagraphAdjust) property.Value; 261 } else if (property.Value instanceof Any) { 262 adjust = (ParagraphAdjust) AnyConverter.toObject(new Type( 263 ParagraphAdjust.class), property.Value); 264 } else { 265 adjust = ParagraphAdjust.fromInt(AnyConverter.toInt( 266 property.Value)); 267 } 268 269 if (adjust != null) { 270 if (adjust.equals(ParagraphAdjust.LEFT)) { 271 StyleConstants.setAlignment(as, 272 StyleConstants.ALIGN_LEFT); 273 } else if (adjust.equals(ParagraphAdjust.RIGHT)) { 274 StyleConstants.setAlignment(as, 275 StyleConstants.ALIGN_RIGHT); 276 } else if (adjust.equals(ParagraphAdjust.CENTER)) { 277 StyleConstants.setAlignment(as, 278 StyleConstants.ALIGN_CENTER); 279 } else if (adjust.equals(ParagraphAdjust.BLOCK) || 280 adjust.equals(ParagraphAdjust.STRETCH)) { 281 StyleConstants.setAlignment(as, 282 StyleConstants.ALIGN_JUSTIFIED); 283 } 284 } else if (Build.DEBUG) { 285 System.err.println( 286 "Invalid property value for key ParaAdjust: " + 287 property.Value.getClass().getName()); 288 } 289 290 // Map background color 291 } else if (property.Name.equals("CharBackColor")) { 292 StyleConstants.setBackground(as, 293 new java.awt.Color(AnyConverter.toInt(property.Value))); 294 295 // FIXME: BidiLevel 296 // Set bold attribute 297 } else if (property.Name.equals("CharWeight")) { 298 boolean isBold = AnyConverter.toFloat(property.Value) > 125; 299 StyleConstants.setBold(as, isBold); 300 301 // FIXME: Java 1.4 ComponentAttribute, ComponentElementName, ComposedTextAttribute 302 // Set FirstLineIndent attribute 303 } else if (property.Name.equals("ParaFirstLineIndent")) { 304 StyleConstants.setFirstLineIndent(as, 305 (float) (toPointFactor * AnyConverter.toInt(property.Value))); 306 307 // Set font family attribute 308 } else if (property.Name.equals("CharFontPitch")) { 309 if (AnyConverter.toShort(property.Value) == 2) { 310 StyleConstants.setFontFamily(as, "Proportional"); 311 } 312 313 // Set font size attribute 314 } else if (property.Name.equals("CharHeight")) { 315 StyleConstants.setFontSize(as, 316 (int) AnyConverter.toFloat(property.Value)); 317 318 // Map foreground color 319 } else if (property.Name.equals("CharColor")) { 320 StyleConstants.setForeground(as, 321 new java.awt.Color(AnyConverter.toInt(property.Value))); 322 323 // FIXME: IconAttribute, IconElementName 324 // Set italic attribute 325 } else if (property.Name.equals("CharPosture")) { 326 FontSlant fs = null; 327 328 if (property.Value instanceof FontSlant) { 329 fs = (FontSlant) property.Value; 330 } else if (property.Value instanceof Any) { 331 fs = (FontSlant) AnyConverter.toObject(new Type( 332 FontSlant.class), property.Value); 333 } 334 335 if (fs != null) { 336 StyleConstants.setItalic(as, FontSlant.ITALIC.equals(fs)); 337 } 338 339 // Set left indent attribute 340 } else if (property.Name.equals("ParaLeftMargin")) { 341 StyleConstants.setLeftIndent(as, 342 (float) (toPointFactor * AnyConverter.toInt(property.Value))); 343 344 // Set right indent attribute 345 } else if (property.Name.equals("ParaRightMargin")) { 346 StyleConstants.setRightIndent(as, 347 (float) (toPointFactor * AnyConverter.toInt(property.Value))); 348 } 349 // Set line spacing attribute 350 else if (property.Name.equals("ParaLineSpacing")) { 351 LineSpacing ls = null; 352 353 if (property.Value instanceof LineSpacing) { 354 ls = (LineSpacing) property.Value; 355 } else if (property.Value instanceof Any) { 356 ls = (LineSpacing) AnyConverter.toObject(new Type( 357 LineSpacing.class), property.Value); 358 } 359 360 if (ls != null) { 361 StyleConstants.setLineSpacing(as, 362 (float) (toPointFactor * ls.Height)); 363 } 364 } 365 // FIXME: Java 1.4 NameAttribute, Orientation, ResolveAttribute 366 // Set space above attribute 367 else if (property.Name.equals("ParaTopMargin")) { 368 StyleConstants.setSpaceAbove(as, 369 (float) (toPointFactor * AnyConverter.toInt(property.Value))); 370 } 371 // Set space below attribute 372 else if (property.Name.equals("ParaBottomMargin")) { 373 StyleConstants.setSpaceBelow(as, 374 (float) (toPointFactor * AnyConverter.toInt(property.Value))); 375 376 // Set strike through attribute 377 } else if (property.Name.equals("CharStrikeout")) { 378 boolean isStrikeThrough = (FontStrikeout.NONE != AnyConverter.toShort(property.Value)); 379 StyleConstants.setStrikeThrough(as, isStrikeThrough); 380 381 // Set sub-/superscript attribute 382 } else if (property.Name.equals("CharEscapement")) { 383 short value = AnyConverter.toShort(property.Value); 384 385 if (value > 0) { 386 StyleConstants.setSuperscript(as, true); 387 } else if (value < 0) { 388 StyleConstants.setSubscript(as, true); 389 } 390 391 // Set tabset attribute 392 } else if (property.Name.equals("ParaTabStops")) { 393 TabStop[] unoTabStops = (TabStop[]) AnyConverter.toArray(property.Value); 394 javax.swing.text.TabStop[] tabStops = new javax.swing.text.TabStop[unoTabStops.length]; 395 396 for (int index2 = 0; index2 < unoTabStops.length; index2++) { 397 float pos = (float) (toPointFactor * unoTabStops[index2].Position); 398 399 if (unoTabStops[index2].Alignment.equals(TabAlign.LEFT)) { 400 tabStops[index2] = new javax.swing.text.TabStop(pos, 401 javax.swing.text.TabStop.ALIGN_LEFT, 402 javax.swing.text.TabStop.LEAD_NONE); 403 } else if (unoTabStops[index2].Alignment.equals( 404 TabAlign.CENTER)) { 405 tabStops[index2] = new javax.swing.text.TabStop(pos, 406 javax.swing.text.TabStop.ALIGN_CENTER, 407 javax.swing.text.TabStop.LEAD_NONE); 408 } else if (unoTabStops[index2].Alignment.equals( 409 TabAlign.RIGHT)) { 410 tabStops[index2] = new javax.swing.text.TabStop(pos, 411 javax.swing.text.TabStop.ALIGN_RIGHT, 412 javax.swing.text.TabStop.LEAD_NONE); 413 } else if (unoTabStops[index2].Alignment.equals( 414 TabAlign.DECIMAL)) { 415 tabStops[index2] = new javax.swing.text.TabStop(pos, 416 javax.swing.text.TabStop.ALIGN_DECIMAL, 417 javax.swing.text.TabStop.LEAD_NONE); 418 } else { 419 tabStops[index2] = new javax.swing.text.TabStop(pos); 420 } 421 } 422 423 // Re-use tabSet object if possible to make AttributeSet.equals work 424 if ((this.tabSet == null) || 425 !java.util.Arrays.equals(tabStops, this.tabStops)) { 426 this.tabStops = tabStops; 427 this.tabSet = new javax.swing.text.TabSet(tabStops); 428 } 429 430 StyleConstants.setTabSet(as, this.tabSet); 431 432 // Set underline attribute 433 } else if (property.Name.equals("CharUnderline")) { 434 boolean isUnderline = (FontUnderline.NONE != AnyConverter.toShort(property.Value)); 435 StyleConstants.setUnderline(as, isUnderline); 436 } 437 } catch (com.sun.star.lang.IllegalArgumentException e) { 438 if (Build.DEBUG) { 439 System.err.println("*** ERROR *** " + e.getClass().getName() + 440 " caught for property " + property.Name + ": " + 441 e.getMessage()); 442 System.err.println(" value is of type " + 443 property.Value.getClass().getName()); 444 } 445 } 446 } 447 448 /** Returns the AttributSet for a given character at a given index */ getCharacterAttribute(int index)449 public javax.swing.text.AttributeSet getCharacterAttribute(int index) { 450 try { 451 com.sun.star.beans.PropertyValue[] propertyValues = unoObject.getCharacterAttributes(index, 452 attributeList); 453 javax.swing.text.SimpleAttributeSet attributeSet = new javax.swing.text.SimpleAttributeSet(); 454 455 if (null != propertyValues) { 456 for (int i = 0; i < propertyValues.length; i++) { 457 setAttribute(attributeSet, propertyValues[i]); 458 } 459 } 460 461 return attributeSet; 462 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 463 if ((index > 0) && (getCharCount() == index)) { 464 return getCharacterAttribute(index - 1); 465 } 466 return null; 467 } 468 } 469 470 /** Given a point in local coordinates, return the zero-based index of the character under that point */ getIndexAtPoint(java.awt.Point point)471 public int getIndexAtPoint(java.awt.Point point) { 472 try { 473 return unoObject.getIndexAtPoint(new Point(point.x, point.y)); 474 } catch (com.sun.star.uno.RuntimeException e) { 475 return -1; 476 } 477 } 478 479 /** Returns the end offset within the selected text */ getSelectionEnd()480 public int getSelectionEnd() { 481 try { 482 int index = unoObject.getSelectionEnd(); 483 484 if (index == -1) { 485 index = getCaretPosition(); 486 } 487 488 return index; 489 } catch (com.sun.star.uno.RuntimeException e) { 490 return -1; 491 } 492 } 493 494 /** Returns the string before a given index 495 * 496 * The Java word iterator has a different understanding of what 497 * a word is than the word iterator used by OOo, so we use the 498 * Java iterators to ensure maximal compatibility with Java. 499 */ getBeforeIndex(int part, int index)500 public java.lang.String getBeforeIndex(int part, int index) { 501 switch (part) { 502 case AccessibleText.CHARACTER: 503 try { 504 String s = unoObject.getText(); 505 return s.substring(index-1, index); 506 } catch (IndexOutOfBoundsException e) { 507 return null; 508 } 509 case AccessibleText.WORD: 510 try { 511 String s = unoObject.getText(); 512 BreakIterator words = BreakIterator.getWordInstance(getLocale(index)); 513 words.setText(s); 514 int end = words.following(index); 515 end = words.previous(); 516 int start = words.previous(); 517 if (start == BreakIterator.DONE) { 518 return null; 519 } 520 return s.substring(start, end); 521 } catch (IllegalArgumentException e) { 522 return null; 523 } catch (IndexOutOfBoundsException e) { 524 return null; 525 } 526 case AccessibleText.SENTENCE: 527 try { 528 String s = unoObject.getText(); 529 BreakIterator sentence = 530 BreakIterator.getSentenceInstance(getLocale(index)); 531 sentence.setText(s); 532 int end = sentence.following(index); 533 end = sentence.previous(); 534 int start = sentence.previous(); 535 if (start == BreakIterator.DONE) { 536 return null; 537 } 538 return s.substring(start, end); 539 } catch (IllegalArgumentException e) { 540 return null; 541 } catch (IndexOutOfBoundsException e) { 542 return null; 543 } 544 case 4: 545 try { 546 TextSegment ts = unoObject.getTextBeforeIndex(index, AccessibleTextType.LINE); 547 return ts.SegmentText; 548 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 549 // Workaround for #104847# 550 if (index > 0 && getCharCount() == index) { 551 return getBeforeIndex(part, index - 1); 552 } 553 return null; 554 } catch (com.sun.star.lang.IllegalArgumentException e) { 555 return null; 556 } 557 case 5: 558 try { 559 TextSegment ts = unoObject.getTextBeforeIndex(index, AccessibleTextType.ATTRIBUTE_RUN); 560 return ts.SegmentText; 561 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 562 return null; 563 } catch (com.sun.star.lang.IllegalArgumentException e) { 564 return null; 565 } 566 default: 567 return null; 568 } 569 } 570 571 572 /** Returns the string at a given index 573 * 574 * The Java word iterator has a different understanding of what 575 * a word is than the word iterator used by OOo, so we use the 576 * Java iterators to ensure maximal compatibility with Java. 577 */ getAtIndex(int part, int index)578 public java.lang.String getAtIndex(int part, int index) { 579 switch (part) { 580 case AccessibleText.CHARACTER: 581 try { 582 String s = unoObject.getText(); 583 return s.substring(index, index + 1); 584 } catch (IndexOutOfBoundsException e) { 585 return null; 586 } 587 case AccessibleText.WORD: 588 try { 589 String s = unoObject.getText(); 590 BreakIterator words = BreakIterator.getWordInstance(getLocale(index)); 591 words.setText(s); 592 int end = words.following(index); 593 return s.substring(words.previous(), end); 594 } catch (IllegalArgumentException e) { 595 return null; 596 } catch (IndexOutOfBoundsException e) { 597 return null; 598 } 599 case AccessibleText.SENTENCE: 600 try { 601 String s = unoObject.getText(); 602 BreakIterator sentence = 603 BreakIterator.getSentenceInstance(getLocale(index)); 604 sentence.setText(s); 605 int end = sentence.following(index); 606 return s.substring(sentence.previous(), end); 607 } catch (IllegalArgumentException e) { 608 return null; 609 } catch (IndexOutOfBoundsException e) { 610 return null; 611 } 612 case 4: 613 try { 614 TextSegment ts = unoObject.getTextAtIndex(index, AccessibleTextType.LINE); 615 return ts.SegmentText; 616 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 617 // Workaround for #104847# 618 if (index > 0 && getCharCount() == index) { 619 return getAtIndex(part, index - 1); 620 } 621 return null; 622 } catch (com.sun.star.lang.IllegalArgumentException e) { 623 return null; 624 } 625 case 5: 626 try { 627 TextSegment ts = unoObject.getTextAtIndex(index, AccessibleTextType.ATTRIBUTE_RUN); 628 return ts.SegmentText; 629 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 630 return null; 631 } catch (com.sun.star.lang.IllegalArgumentException e) { 632 return null; 633 } 634 635 default: 636 return null; 637 } 638 } 639 640 /** Returns the number of characters (valid indicies) */ getCharCount()641 public int getCharCount() { 642 try { 643 return unoObject.getCharacterCount(); 644 } catch (com.sun.star.uno.RuntimeException e) { 645 } 646 647 return 0; 648 } 649 650 /** Returns the portion of the text that is selected */ getSelectedText()651 public java.lang.String getSelectedText() { 652 try { 653 return unoObject.getSelectedText(); 654 } catch (com.sun.star.uno.RuntimeException e) { 655 } 656 657 return null; 658 } 659 660 /** Determines the bounding box of the character at the given index into the string */ getCharacterBounds(int index)661 public java.awt.Rectangle getCharacterBounds(int index) { 662 try { 663 Rectangle unoRect = unoObject.getCharacterBounds(index); 664 return new java.awt.Rectangle(unoRect.X, unoRect.Y, unoRect.Width, unoRect.Height); 665 } catch (com.sun.star.lang.IndexOutOfBoundsException e) { 666 if ((index > 0) && (getCharCount() == index)) { 667 return getCharacterBounds(index - 1); 668 } 669 } catch (com.sun.star.uno.RuntimeException e) { 670 } 671 672 return new java.awt.Rectangle(); 673 } 674 } 675