Showing the changes to java/pkg/javax/swing/text/TextLayoutStrategy.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,555 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1999, 2008, 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 | package javax.swing.text; | ||
| 26 | |||
| 27 | import java.util.*; | ||
| 28 | import java.awt.*; | ||
| 29 | import java.text.AttributedCharacterIterator; | ||
| 30 | import java.text.BreakIterator; | ||
| 31 | import java.awt.font.*; | ||
| 32 | import java.awt.geom.AffineTransform; | ||
| 33 | import javax.swing.JComponent; | ||
| 34 | import javax.swing.event.DocumentEvent; | ||
| 35 | import sun.font.BidiUtils; | ||
| 36 | |||
| 37 | /** | ||
| 38 | * A flow strategy that uses java.awt.font.LineBreakMeasureer to | ||
| 39 | * produce java.awt.font.TextLayout for i18n capable rendering. | ||
| 40 | * If the child view being placed into the flow is of type | ||
| 41 | * GlyphView and can be rendered by TextLayout, a GlyphPainter | ||
| 42 | * that uses TextLayout is plugged into the GlyphView. | ||
| 43 | * | ||
| 44 | * @author Timothy Prinzing | ||
| 45 | */ | ||
| 46 | class TextLayoutStrategy extends FlowView.FlowStrategy { | ||
| 47 | |||
| 48 | /** | ||
| 49 | * Constructs a layout strategy for paragraphs based | ||
| 50 | * upon java.awt.font.LineBreakMeasurer. | ||
| 51 | */ | ||
| 52 | public TextLayoutStrategy() { | ||
| 53 | text = new AttributedSegment(); | ||
| 54 | } | ||
| 55 | |||
| 56 | // --- FlowStrategy methods -------------------------------------------- | ||
| 57 | |||
| 58 | /** | ||
| 59 | * Gives notification that something was inserted into the document | ||
| 60 | * in a location that the given flow view is responsible for. The | ||
| 61 | * strategy should update the appropriate changed region (which | ||
| 62 | * depends upon the strategy used for repair). | ||
| 63 | * | ||
| 64 | * @param e the change information from the associated document | ||
| 65 | * @param alloc the current allocation of the view inside of the insets. | ||
| 66 | * This value will be null if the view has not yet been displayed. | ||
| 67 | * @see View#insertUpdate | ||
| 68 | */ | ||
| 69 | public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { | ||
| 70 | sync(fv); | ||
| 71 | super.insertUpdate(fv, e, alloc); | ||
| 72 | } | ||
| 73 | |||
| 74 | /** | ||
| 75 | * Gives notification that something was removed from the document | ||
| 76 | * in a location that the given flow view is responsible for. | ||
| 77 | * | ||
| 78 | * @param e the change information from the associated document | ||
| 79 | * @param alloc the current allocation of the view inside of the insets. | ||
| 80 | * @see View#removeUpdate | ||
| 81 | */ | ||
| 82 | public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { | ||
| 83 | sync(fv); | ||
| 84 | super.removeUpdate(fv, e, alloc); | ||
| 85 | } | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Gives notification from the document that attributes were changed | ||
| 89 | * in a location that this view is responsible for. | ||
| 90 | * | ||
| 91 | * @param changes the change information from the associated document | ||
| 92 | * @param a the current allocation of the view | ||
| 93 | * @param f the factory to use to rebuild if the view has children | ||
| 94 | * @see View#changedUpdate | ||
| 95 | */ | ||
| 96 | public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) { | ||
| 97 | sync(fv); | ||
| 98 | super.changedUpdate(fv, e, alloc); | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Does a a full layout on the given View. This causes all of | ||
| 103 | * the rows (child views) to be rebuilt to match the given | ||
| 104 | * constraints for each row. This is called by a FlowView.layout | ||
| 105 | * to update the child views in the flow. | ||
| 106 | * | ||
| 107 | * @param fv the view to reflow | ||
| 108 | */ | ||
| 109 | public void layout(FlowView fv) { | ||
| 110 | super.layout(fv); | ||
| 111 | } | ||
| 112 | |||
| 113 | /** | ||
| 114 | * Creates a row of views that will fit within the | ||
| 115 | * layout span of the row. This is implemented to execute the | ||
| 116 | * superclass functionality (which fills the row with child | ||
| 117 | * views or view fragments) and follow that with bidi reordering | ||
| 118 | * of the unidirectional view fragments. | ||
| 119 | * | ||
| 120 | * @param row the row to fill in with views. This is assumed | ||
| 121 | * to be empty on entry. | ||
| 122 | * @param pos The current position in the children of | ||
| 123 | * this views element from which to start. | ||
| 124 | * @return the position to start the next row | ||
| 125 | */ | ||
| 126 | protected int layoutRow(FlowView fv, int rowIndex, int p0) { | ||
| 127 | int p1 = super.layoutRow(fv, rowIndex, p0); | ||
| 128 | View row = fv.getView(rowIndex); | ||
| 129 | Document doc = fv.getDocument(); | ||
| 130 | Object i18nFlag = doc.getProperty(AbstractDocument.I18NProperty); | ||
| 131 | if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) { | ||
| 132 | int n = row.getViewCount(); | ||
| 133 | if (n > 1) { | ||
| 134 | AbstractDocument d = (AbstractDocument)fv.getDocument(); | ||
| 135 | Element bidiRoot = d.getBidiRootElement(); | ||
| 136 | byte[] levels = new byte[n]; | ||
| 137 | View[] reorder = new View[n]; | ||
| 138 | |||
| 139 | for( int i=0; i<n; i++ ) { | ||
| 140 | View v = row.getView(i); | ||
| 141 | int bidiIndex =bidiRoot.getElementIndex(v.getStartOffset()); | ||
| 142 | Element bidiElem = bidiRoot.getElement( bidiIndex ); | ||
| 143 | levels[i] = (byte)StyleConstants.getBidiLevel(bidiElem.getAttributes()); | ||
| 144 | reorder[i] = v; | ||
| 145 | } | ||
| 146 | |||
| 147 | BidiUtils.reorderVisually( levels, reorder ); | ||
| 148 | row.replace(0, n, reorder); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | return p1; | ||
| 152 | } | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Adjusts the given row if possible to fit within the | ||
| 156 | * layout span. Since all adjustments were already | ||
| 157 | * calculated by the LineBreakMeasurer, this is implemented | ||
| 158 | * to do nothing. | ||
| 159 | * | ||
| 160 | * @param r the row to adjust to the current layout | ||
| 161 | * span. | ||
| 162 | * @param desiredSpan the current layout span >= 0 | ||
| 163 | * @param x the location r starts at. | ||
| 164 | */ | ||
| 165 | protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) { | ||
| 166 | } | ||
| 167 | |||
| 168 | /** | ||
| 169 | * Creates a unidirectional view that can be used to represent the | ||
| 170 | * current chunk. This can be either an entire view from the | ||
| 171 | * logical view, or a fragment of the view. | ||
| 172 | * | ||
| 173 | * @param fv the view holding the flow | ||
| 174 | * @param startOffset the start location for the view being created | ||
| 175 | * @param spanLeft the about of span left to fill in the row | ||
| 176 | * @param rowIndex the row the view will be placed into | ||
| 177 | */ | ||
| 178 | protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) { | ||
| 179 | // Get the child view that contains the given starting position | ||
| 180 | View lv = getLogicalView(fv); | ||
| 181 | View row = fv.getView(rowIndex); | ||
| 182 | boolean requireNextWord = (viewBuffer.size() == 0) ? false : true; | ||
| 183 | int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward); | ||
| 184 | View v = lv.getView(childIndex); | ||
| 185 | |||
| 186 | int endOffset = getLimitingOffset(v, startOffset, spanLeft, requireNextWord); | ||
| 187 | if (endOffset == startOffset) { | ||
| 188 | return null; | ||
| 189 | } | ||
| 190 | |||
| 191 | View frag; | ||
| 192 | if ((startOffset==v.getStartOffset()) && (endOffset == v.getEndOffset())) { | ||
| 193 | // return the entire view | ||
| 194 | frag = v; | ||
| 195 | } else { | ||
| 196 | // return a unidirectional fragment. | ||
| 197 | frag = v.createFragment(startOffset, endOffset); | ||
| 198 | } | ||
| 199 | |||
| 200 | if ((frag instanceof GlyphView) && (measurer != null)) { | ||
| 201 | // install a TextLayout based renderer if the view is responsible | ||
| 202 | // for glyphs. If the view represents a tab, the default | ||
| 203 | // glyph painter is used (may want to handle tabs differently). | ||
| 204 | boolean isTab = false; | ||
| 205 | int p0 = frag.getStartOffset(); | ||
| 206 | int p1 = frag.getEndOffset(); | ||
| 207 | if ((p1 - p0) == 1) { | ||
| 208 | // check for tab | ||
| 209 | Segment s = ((GlyphView)frag).getText(p0, p1); | ||
| 210 | char ch = s.first(); | ||
| 211 | if (ch == '\t') { | ||
| 212 | isTab = true; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | TextLayout tl = (isTab) ? null : | ||
| 216 | measurer.nextLayout(spanLeft, text.toIteratorIndex(endOffset), | ||
| 217 | requireNextWord); | ||
| 218 | if (tl != null) { | ||
| 219 | ((GlyphView)frag).setGlyphPainter(new GlyphPainter2(tl)); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | return frag; | ||
| 223 | } | ||
| 224 | |||
| 225 | /** | ||
| 226 | * Calculate the limiting offset for the next view fragment. | ||
| 227 | * At most this would be the entire view (i.e. the limiting | ||
| 228 | * offset would be the end offset in that case). If the range | ||
| 229 | * contains a tab or a direction change, that will limit the | ||
| 230 | * offset to something less. This value is then fed to the | ||
| 231 | * LineBreakMeasurer as a limit to consider in addition to the | ||
| 232 | * remaining span. | ||
| 233 | * | ||
| 234 | * @param v the logical view representing the starting offset. | ||
| 235 | * @param startOffset the model location to start at. | ||
| 236 | */ | ||
| 237 | int getLimitingOffset(View v, int startOffset, int spanLeft, boolean requireNextWord) { | ||
| 238 | int endOffset = v.getEndOffset(); | ||
| 239 | |||
| 240 | // check for direction change | ||
| 241 | Document doc = v.getDocument(); | ||
| 242 | if (doc instanceof AbstractDocument) { | ||
| 243 | AbstractDocument d = (AbstractDocument) doc; | ||
| 244 | Element bidiRoot = d.getBidiRootElement(); | ||
| 245 | if( bidiRoot.getElementCount() > 1 ) { | ||
| 246 | int bidiIndex = bidiRoot.getElementIndex( startOffset ); | ||
| 247 | Element bidiElem = bidiRoot.getElement( bidiIndex ); | ||
| 248 | endOffset = Math.min( bidiElem.getEndOffset(), endOffset ); | ||
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | // check for tab | ||
| 253 | if (v instanceof GlyphView) { | ||
| 254 | Segment s = ((GlyphView)v).getText(startOffset, endOffset); | ||
| 255 | char ch = s.first(); | ||
| 256 | if (ch == '\t') { | ||
| 257 | // if the first character is a tab, create a dedicated | ||
| 258 | // view for just the tab | ||
| 259 | endOffset = startOffset + 1; | ||
| 260 | } else { | ||
| 261 | for (ch = s.next(); ch != Segment.DONE; ch = s.next()) { | ||
| 262 | if (ch == '\t') { | ||
| 263 | // found a tab, don't include it in the text | ||
| 264 | endOffset = startOffset + s.getIndex() - s.getBeginIndex(); | ||
| 265 | break; | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | } | ||
| 270 | |||
| 271 | // determine limit from LineBreakMeasurer | ||
| 272 | int limitIndex = text.toIteratorIndex(endOffset); | ||
| 273 | if (measurer != null) { | ||
| 274 | int index = text.toIteratorIndex(startOffset); | ||
| 275 | if (measurer.getPosition() != index) { | ||
| 276 | measurer.setPosition(index); | ||
| 277 | } | ||
| 278 | limitIndex = measurer.nextOffset(spanLeft, limitIndex, requireNextWord); | ||
| 279 | } | ||
| 280 | int pos = text.toModelPosition(limitIndex); | ||
| 281 | return pos; | ||
| 282 | } | ||
| 283 | |||
| 284 | /** | ||
| 285 | * Synchronize the strategy with its FlowView. Allows the strategy | ||
| 286 | * to update its state to account for changes in that portion of the | ||
| 287 | * model represented by the FlowView. Also allows the strategy | ||
| 288 | * to update the FlowView in response to these changes. | ||
| 289 | */ | ||
| 290 | void sync(FlowView fv) { | ||
| 291 | View lv = getLogicalView(fv); | ||
| 292 | text.setView(lv); | ||
| 293 | |||
| 294 | Container container = fv.getContainer(); | ||
| 295 | FontRenderContext frc = sun.swing.SwingUtilities2. | ||
| 296 | getFontRenderContext(container); | ||
| 297 | BreakIterator iter; | ||
| 298 | Container c = fv.getContainer(); | ||
| 299 | if (c != null) { | ||
| 300 | iter = BreakIterator.getLineInstance(c.getLocale()); | ||
| 301 | } else { | ||
| 302 | iter = BreakIterator.getLineInstance(); | ||
| 303 | } | ||
| 304 | |||
| 305 | Object shaper = null; | ||
| 306 | if (c instanceof JComponent) { | ||
| 307 | shaper = ((JComponent) c).getClientProperty( | ||
| 308 | TextAttribute.NUMERIC_SHAPING); | ||
| 309 | } | ||
| 310 | text.setShaper(shaper); | ||
| 311 | |||
| 312 | measurer = new LineBreakMeasurer(text, iter, frc); | ||
| 313 | |||
| 314 | // If the children of the FlowView's logical view are GlyphViews, they | ||
| 315 | // need to have their painters updated. | ||
| 316 | int n = lv.getViewCount(); | ||
| 317 | for( int i=0; i<n; i++ ) { | ||
| 318 | View child = lv.getView(i); | ||
| 319 | if( child instanceof GlyphView ) { | ||
| 320 | int p0 = child.getStartOffset(); | ||
| 321 | int p1 = child.getEndOffset(); | ||
| 322 | measurer.setPosition(text.toIteratorIndex(p0)); | ||
| 323 | TextLayout layout | ||
| 324 | = measurer.nextLayout( Float.MAX_VALUE, | ||
| 325 | text.toIteratorIndex(p1), false ); | ||
| 326 | ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout)); | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | // Reset measurer. | ||
| 331 | measurer.setPosition(text.getBeginIndex()); | ||
| 332 | |||
| 333 | } | ||
| 334 | |||
| 335 | // --- variables ------------------------------------------------------- | ||
| 336 | |||
| 337 | private LineBreakMeasurer measurer; | ||
| 338 | private AttributedSegment text; | ||
| 339 | |||
| 340 | /** | ||
| 341 | * Implementation of AttributedCharacterIterator that supports | ||
| 342 | * the GlyphView attributes for rendering the glyphs through a | ||
| 343 | * TextLayout. | ||
| 344 | */ | ||
| 345 | static class AttributedSegment extends Segment implements AttributedCharacterIterator { | ||
| 346 | |||
| 347 | AttributedSegment() { | ||
| 348 | } | ||
| 349 | |||
| 350 | View getView() { | ||
| 351 | return v; | ||
| 352 | } | ||
| 353 | |||
| 354 | void setView(View v) { | ||
| 355 | this.v = v; | ||
| 356 | Document doc = v.getDocument(); | ||
| 357 | int p0 = v.getStartOffset(); | ||
| 358 | int p1 = v.getEndOffset(); | ||
| 359 | try { | ||
| 360 | doc.getText(p0, p1 - p0, this); | ||
| 361 | } catch (BadLocationException bl) { | ||
| 362 | throw new IllegalArgumentException("Invalid view"); | ||
| 363 | } | ||
| 364 | first(); | ||
| 365 | } | ||
| 366 | |||
| 367 | /** | ||
| 368 | * Get a boundary position for the font. | ||
| 369 | * This is implemented to assume that two fonts are | ||
| 370 | * equal if their references are equal (i.e. that the | ||
| 371 | * font came from a cache). | ||
| 372 | * | ||
| 373 | * @return the location in model coordinates. This is | ||
| 374 | * not the same as the Segment coordinates. | ||
| 375 | */ | ||
| 376 | int getFontBoundary(int childIndex, int dir) { | ||
| 377 | View child = v.getView(childIndex); | ||
| 378 | Font f = getFont(childIndex); | ||
| 379 | for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount()); | ||
| 380 | childIndex += dir) { | ||
| 381 | Font next = getFont(childIndex); | ||
| 382 | if (next != f) { | ||
| 383 | // this run is different | ||
| 384 | break; | ||
| 385 | } | ||
| 386 | child = v.getView(childIndex); | ||
| 387 | } | ||
| 388 | return (dir < 0) ? child.getStartOffset() : child.getEndOffset(); | ||
| 389 | } | ||
| 390 | |||
| 391 | /** | ||
| 392 | * Get the font at the given child index. | ||
| 393 | */ | ||
| 394 | Font getFont(int childIndex) { | ||
| 395 | View child = v.getView(childIndex); | ||
| 396 | if (child instanceof GlyphView) { | ||
| 397 | return ((GlyphView)child).getFont(); | ||
| 398 | } | ||
| 399 | return null; | ||
| 400 | } | ||
| 401 | |||
| 402 | int toModelPosition(int index) { | ||
| 403 | return v.getStartOffset() + (index - getBeginIndex()); | ||
| 404 | } | ||
| 405 | |||
| 406 | int toIteratorIndex(int pos) { | ||
| 407 | return pos - v.getStartOffset() + getBeginIndex(); | ||
| 408 | } | ||
| 409 | |||
| 410 | private void setShaper(Object shaper) { | ||
| 411 | this.shaper = shaper; | ||
| 412 | } | ||
| 413 | |||
| 414 | // --- AttributedCharacterIterator methods ------------------------- | ||
| 415 | |||
| 416 | /** | ||
| 417 | * Returns the index of the first character of the run | ||
| 418 | * with respect to all attributes containing the current character. | ||
| 419 | */ | ||
| 420 | public int getRunStart() { | ||
| 421 | int pos = toModelPosition(getIndex()); | ||
| 422 | int i = v.getViewIndex(pos, Position.Bias.Forward); | ||
| 423 | View child = v.getView(i); | ||
| 424 | return toIteratorIndex(child.getStartOffset()); | ||
| 425 | } | ||
| 426 | |||
| 427 | /** | ||
| 428 | * Returns the index of the first character of the run | ||
| 429 | * with respect to the given attribute containing the current character. | ||
| 430 | */ | ||
| 431 | public int getRunStart(AttributedCharacterIterator.Attribute attribute) { | ||
| 432 | if (attribute instanceof TextAttribute) { | ||
| 433 | int pos = toModelPosition(getIndex()); | ||
| 434 | int i = v.getViewIndex(pos, Position.Bias.Forward); | ||
| 435 | if (attribute == TextAttribute.FONT) { | ||
| 436 | return toIteratorIndex(getFontBoundary(i, -1)); | ||
| 437 | } | ||
| 438 | } | ||
| 439 | return getBeginIndex(); | ||
| 440 | } | ||
| 441 | |||
| 442 | /** | ||
| 443 | * Returns the index of the first character of the run | ||
| 444 | * with respect to the given attributes containing the current character. | ||
| 445 | */ | ||
| 446 | public int getRunStart(Set<? extends Attribute> attributes) { | ||
| 447 | int index = getBeginIndex(); | ||
| 448 | Object[] a = attributes.toArray(); | ||
| 449 | for (int i = 0; i < a.length; i++) { | ||
| 450 | TextAttribute attr = (TextAttribute) a[i]; | ||
| 451 | index = Math.max(getRunStart(attr), index); | ||
| 452 | } | ||
| 453 | return Math.min(getIndex(), index); | ||
| 454 | } | ||
| 455 | |||
| 456 | /** | ||
| 457 | * Returns the index of the first character following the run | ||
| 458 | * with respect to all attributes containing the current character. | ||
| 459 | */ | ||
| 460 | public int getRunLimit() { | ||
| 461 | int pos = toModelPosition(getIndex()); | ||
| 462 | int i = v.getViewIndex(pos, Position.Bias.Forward); | ||
| 463 | View child = v.getView(i); | ||
| 464 | return toIteratorIndex(child.getEndOffset()); | ||
| 465 | } | ||
| 466 | |||
| 467 | /** | ||
| 468 | * Returns the index of the first character following the run | ||
| 469 | * with respect to the given attribute containing the current character. | ||
| 470 | */ | ||
| 471 | public int getRunLimit(AttributedCharacterIterator.Attribute attribute) { | ||
| 472 | if (attribute instanceof TextAttribute) { | ||
| 473 | int pos = toModelPosition(getIndex()); | ||
| 474 | int i = v.getViewIndex(pos, Position.Bias.Forward); | ||
| 475 | if (attribute == TextAttribute.FONT) { | ||
| 476 | return toIteratorIndex(getFontBoundary(i, 1)); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | return getEndIndex(); | ||
| 480 | } | ||
| 481 | |||
| 482 | /** | ||
| 483 | * Returns the index of the first character following the run | ||
| 484 | * with respect to the given attributes containing the current character. | ||
| 485 | */ | ||
| 486 | public int getRunLimit(Set<? extends Attribute> attributes) { | ||
| 487 | int index = getEndIndex(); | ||
| 488 | Object[] a = attributes.toArray(); | ||
| 489 | for (int i = 0; i < a.length; i++) { | ||
| 490 | TextAttribute attr = (TextAttribute) a[i]; | ||
| 491 | index = Math.min(getRunLimit(attr), index); | ||
| 492 | } | ||
| 493 | return Math.max(getIndex(), index); | ||
| 494 | } | ||
| 495 | |||
| 496 | /** | ||
| 497 | * Returns a map with the attributes defined on the current | ||
| 498 | * character. | ||
| 499 | */ | ||
| 500 | public Map<Attribute, Object> getAttributes() { | ||
| 501 | Object[] ka = keys.toArray(); | ||
| 502 | Hashtable<Attribute, Object> h = new Hashtable<Attribute, Object>(); | ||
| 503 | for (int i = 0; i < ka.length; i++) { | ||
| 504 | TextAttribute a = (TextAttribute) ka[i]; | ||
| 505 | Object value = getAttribute(a); | ||
| 506 | if (value != null) { | ||
| 507 | h.put(a, value); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | return h; | ||
| 511 | } | ||
| 512 | |||
| 513 | /** | ||
| 514 | * Returns the value of the named attribute for the current character. | ||
| 515 | * Returns null if the attribute is not defined. | ||
| 516 | * @param attribute the key of the attribute whose value is requested. | ||
| 517 | */ | ||
| 518 | public Object getAttribute(AttributedCharacterIterator.Attribute attribute) { | ||
| 519 | int pos = toModelPosition(getIndex()); | ||
| 520 | int childIndex = v.getViewIndex(pos, Position.Bias.Forward); | ||
| 521 | if (attribute == TextAttribute.FONT) { | ||
| 522 | return getFont(childIndex); | ||
| 523 | } else if( attribute == TextAttribute.RUN_DIRECTION ) { | ||
| 524 | return | ||
| 525 | v.getDocument().getProperty(TextAttribute.RUN_DIRECTION); | ||
| 526 | } else if (attribute == TextAttribute.NUMERIC_SHAPING) { | ||
| 527 | return shaper; | ||
| 528 | } | ||
| 529 | return null; | ||
| 530 | } | ||
| 531 | |||
| 532 | /** | ||
| 533 | * Returns the keys of all attributes defined on the | ||
| 534 | * iterator's text range. The set is empty if no | ||
| 535 | * attributes are defined. | ||
| 536 | */ | ||
| 537 | public Set<Attribute> getAllAttributeKeys() { | ||
| 538 | return keys; | ||
| 539 | } | ||
| 540 | |||
| 541 | View v; | ||
| 542 | |||
| 543 | static Set<Attribute> keys; | ||
| 544 | |||
| 545 | static { | ||
| 546 | keys = new HashSet<Attribute>(); | ||
| 547 | keys.add(TextAttribute.FONT); | ||
| 548 | keys.add(TextAttribute.RUN_DIRECTION); | ||
| 549 | keys.add(TextAttribute.NUMERIC_SHAPING); | ||
| 550 | } | ||
| 551 | |||
| 552 | private Object shaper = null; | ||
| 553 | } | ||
| 554 | |||
| 555 | } | ||