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