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 package javax.swing.text; 26 27 import java.text.*; 28 import java.awt.*; 29 import java.awt.font.*; 30 import java.awt.geom.Rectangle2D; 31 32 /** 33 * A class to perform rendering of the glyphs. 34 * This can be implemented to be stateless, or 35 * to hold some information as a cache to 36 * facilitate faster rendering and model/view 37 * translation. At a minimum, the GlyphPainter 38 * allows a View implementation to perform its 39 * duties independent of a particular version 40 * of JVM and selection of capabilities (i.e. 41 * shaping for i18n, etc). 42 * <p> 43 * This implementation is intended for operation 44 * under the JDK. It uses the 45 * java.awt.font.TextLayout class to do i18n capable 46 * rendering. 47 * 48 * @author Timothy Prinzing 49 * @see GlyphView 50 */ 51 class GlyphPainter2 extends GlyphView.GlyphPainter { 52 53 public GlyphPainter2(TextLayout layout) { 54 this.layout = layout; 55 } 56 57 /** 58 * Create a painter to use for the given GlyphView. 59 */ 60 public GlyphView.GlyphPainter getPainter(GlyphView v, int p0, int p1) { 61 return null; 62 } 63 64 /** 65 * Determine the span the glyphs given a start location 66 * (for tab expansion). This implementation assumes it 67 * has no tabs (i.e. TextLayout doesn't deal with tab 68 * expansion). 69 */ 70 public float getSpan(GlyphView v, int p0, int p1, 71 TabExpander e, float x) { 72 73 if ((p0 == v.getStartOffset()) && (p1 == v.getEndOffset())) { 74 return layout.getAdvance(); 75 } 76 int p = v.getStartOffset(); 77 int index0 = p0 - p; 78 int index1 = p1 - p; 79 80 TextHitInfo hit0 = TextHitInfo.afterOffset(index0); 81 TextHitInfo hit1 = TextHitInfo.beforeOffset(index1); 82 float[] locs = layout.getCaretInfo(hit0); 83 float x0 = locs[0]; 84 locs = layout.getCaretInfo(hit1); 85 float x1 = locs[0]; 86 return (x1 > x0) ? x1 - x0 : x0 - x1; 87 } 88 89 public float getHeight(GlyphView v) { 90 return layout.getAscent() + layout.getDescent() + layout.getLeading(); 91 } 92 93 /** 94 * Fetch the ascent above the baseline for the glyphs 95 * corresponding to the given range in the model. 96 */ 97 public float getAscent(GlyphView v) { 98 return layout.getAscent(); 99 } 100 101 /** 102 * Fetch the descent below the baseline for the glyphs 103 * corresponding to the given range in the model. 104 */ 105 public float getDescent(GlyphView v) { 106 return layout.getDescent(); 107 } 108 109 /** 110 * Paint the glyphs for the given view. This is implemented 111 * to only render if the Graphics is of type Graphics2D which 112 * is required by TextLayout (and this should be the case if 113 * running on the JDK). 114 */ 115 public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) { 116 if (g instanceof Graphics2D) { 117 Rectangle2D alloc = a.getBounds2D(); 118 Graphics2D g2d = (Graphics2D)g; 119 float y = (float) alloc.getY() + layout.getAscent() + layout.getLeading(); 120 float x = (float) alloc.getX(); 121 if( p0 > v.getStartOffset() || p1 < v.getEndOffset() ) { 122 try { 123 //TextLayout can't render only part of it's range, so if a 124 //partial range is required, add a clip region. 125 Shape s = v.modelToView(p0, Position.Bias.Forward, 126 p1, Position.Bias.Backward, a); 127 Shape savedClip = g.getClip(); 128 g2d.clip(s); 129 layout.draw(g2d, x, y); 130 g.setClip(savedClip); 131 } catch (BadLocationException e) {} 132 } else { 133 layout.draw(g2d, x, y); 134 } 135 } 136 } 137 138 public Shape modelToView(GlyphView v, int pos, Position.Bias bias, 139 Shape a) throws BadLocationException { 140 int offs = pos - v.getStartOffset(); 141 Rectangle2D alloc = a.getBounds2D(); 142 TextHitInfo hit = (bias == Position.Bias.Forward) ? 143 TextHitInfo.afterOffset(offs) : TextHitInfo.beforeOffset(offs); 144 float[] locs = layout.getCaretInfo(hit); 145 146 // vertical at the baseline, should use slope and check if glyphs 147 // are being rendered vertically. 148 alloc.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight()); 149 return alloc; 150 } 151 152 /** 153 * Provides a mapping from the view coordinate space to the logical 154 * coordinate space of the model. 155 * 156 * @param v the view containing the view coordinates 157 * @param x the X coordinate 158 * @param y the Y coordinate 159 * @param a the allocated region to render into 160 * @param biasReturn either {@code Position.Bias.Forward} 161 * or {@code Position.Bias.Backward} is returned as the 162 * zero-th element of this array 163 * @return the location within the model that best represents the 164 * given point of view 165 * @see View#viewToModel 166 */ 167 public int viewToModel(GlyphView v, float x, float y, Shape a, 168 Position.Bias[] biasReturn) { 169 170 Rectangle2D alloc = (a instanceof Rectangle2D) ? (Rectangle2D)a : a.getBounds2D(); 171 //Move the y co-ord of the hit onto the baseline. This is because TextLayout supports 172 //italic carets and we do not. 173 TextHitInfo hit = layout.hitTestChar(x - (float)alloc.getX(), 0); 174 int pos = hit.getInsertionIndex(); 175 176 if (pos == v.getEndOffset()) { 177 pos--; 178 } 179 180 biasReturn[0] = hit.isLeadingEdge() ? Position.Bias.Forward : Position.Bias.Backward; 181 return pos + v.getStartOffset(); 182 } 183 184 /** 185 * Determines the model location that represents the 186 * maximum advance that fits within the given span. 187 * This could be used to break the given view. The result 188 * should be a location just shy of the given advance. This 189 * differs from viewToModel which returns the closest 190 * position which might be proud of the maximum advance. 191 * 192 * @param v the view to find the model location to break at. 193 * @param p0 the location in the model where the 194 * fragment should start it's representation >= 0. 195 * @param pos the graphic location along the axis that the 196 * broken view would occupy >= 0. This may be useful for 197 * things like tab calculations. 198 * @param len specifies the distance into the view 199 * where a potential break is desired >= 0. 200 * @return the maximum model location possible for a break. 201 * @see View#breakView 202 */ 203 public int getBoundedPosition(GlyphView v, int p0, float x, float len) { 204 if( len < 0 ) 205 throw new IllegalArgumentException("Length must be >= 0."); 206 // note: this only works because swing uses TextLayouts that are 207 // only pure rtl or pure ltr 208 TextHitInfo hit; 209 if (layout.isLeftToRight()) { 210 hit = layout.hitTestChar(len, 0); 211 } else { 212 hit = layout.hitTestChar(layout.getAdvance() - len, 0); 213 } 214 return v.getStartOffset() + hit.getCharIndex(); 215 } 216 217 /** 218 * Provides a way to determine the next visually represented model 219 * location that one might place a caret. Some views may not be 220 * visible, they might not be in the same order found in the model, or 221 * they just might not allow access to some of the locations in the 222 * model. 223 * 224 * @param v the view to use 225 * @param pos the position to convert >= 0 226 * @param a the allocated region to render into 227 * @param direction the direction from the current position that can 228 * be thought of as the arrow keys typically found on a keyboard. 229 * This may be SwingConstants.WEST, SwingConstants.EAST, 230 * SwingConstants.NORTH, or SwingConstants.SOUTH. 231 * @return the location within the model that best represents the next 232 * location visual position. 233 * @exception BadLocationException 234 * @exception IllegalArgumentException for an invalid direction 235 */ 236 public int getNextVisualPositionFrom(GlyphView v, int pos, 237 Position.Bias b, Shape a, 238 int direction, 239 Position.Bias[] biasRet) 240 throws BadLocationException { 241 242 Document doc = v.getDocument(); 243 int startOffset = v.getStartOffset(); 244 int endOffset = v.getEndOffset(); 245 Segment text; 246 boolean viewIsLeftToRight; 247 TextHitInfo currentHit, nextHit; 248 249 switch (direction) { 250 case View.NORTH: 251 break; 252 case View.SOUTH: 253 break; 254 case View.EAST: 255 viewIsLeftToRight = AbstractDocument.isLeftToRight(doc, startOffset, endOffset); 256 257 if(startOffset == doc.getLength()) { 258 if(pos == -1) { 259 biasRet[0] = Position.Bias.Forward; 260 return startOffset; 261 } 262 // End case for bidi text where newline is at beginning 263 // of line. 264 return -1; 265 } 266 if(pos == -1) { 267 // Entering view from the left. 268 if( viewIsLeftToRight ) { 269 biasRet[0] = Position.Bias.Forward; 270 return startOffset; 271 } else { 272 text = v.getText(endOffset - 1, endOffset); 273 char c = text.array[text.offset]; 274 SegmentCache.releaseSharedSegment(text); 275 if(c == '\n') { 276 biasRet[0] = Position.Bias.Forward; 277 return endOffset-1; 278 } 279 biasRet[0] = Position.Bias.Backward; 280 return endOffset; 281 } 282 } 283 if( b==Position.Bias.Forward ) 284 currentHit = TextHitInfo.afterOffset(pos-startOffset); 285 else 286 currentHit = TextHitInfo.beforeOffset(pos-startOffset); 287 nextHit = layout.getNextRightHit(currentHit); 288 if( nextHit == null ) { 289 return -1; 290 } 291 if( viewIsLeftToRight != layout.isLeftToRight() ) { 292 // If the layout's base direction is different from 293 // this view's run direction, we need to use the weak 294 // carrat. 295 nextHit = layout.getVisualOtherHit(nextHit); 296 } 297 pos = nextHit.getInsertionIndex() + startOffset; 298 299 if(pos == endOffset) { 300 // A move to the right from an internal position will 301 // only take us to the endOffset in a left to right run. 302 text = v.getText(endOffset - 1, endOffset); 303 char c = text.array[text.offset]; 304 SegmentCache.releaseSharedSegment(text); 305 if(c == '\n') { 306 return -1; 307 } 308 biasRet[0] = Position.Bias.Backward; 309 } 310 else { 311 biasRet[0] = Position.Bias.Forward; 312 } 313 return pos; 314 case View.WEST: 315 viewIsLeftToRight = AbstractDocument.isLeftToRight(doc, startOffset, endOffset); 316 317 if(startOffset == doc.getLength()) { 318 if(pos == -1) { 319 biasRet[0] = Position.Bias.Forward; 320 return startOffset; 321 } 322 // End case for bidi text where newline is at beginning 323 // of line. 324 return -1; 325 } 326 if(pos == -1) { 327 // Entering view from the right 328 if( viewIsLeftToRight ) { 329 text = v.getText(endOffset - 1, endOffset); 330 char c = text.array[text.offset]; 331 SegmentCache.releaseSharedSegment(text); 332 if ((c == '\n') || Character.isSpaceChar(c)) { 333 biasRet[0] = Position.Bias.Forward; 334 return endOffset - 1; 335 } 336 biasRet[0] = Position.Bias.Backward; 337 return endOffset; 338 } else { 339 biasRet[0] = Position.Bias.Forward; 340 return startOffset; 341 } 342 } 343 if( b==Position.Bias.Forward ) 344 currentHit = TextHitInfo.afterOffset(pos-startOffset); 345 else 346 currentHit = TextHitInfo.beforeOffset(pos-startOffset); 347 nextHit = layout.getNextLeftHit(currentHit); 348 if( nextHit == null ) { 349 return -1; 350 } 351 if( viewIsLeftToRight != layout.isLeftToRight() ) { 352 // If the layout's base direction is different from 353 // this view's run direction, we need to use the weak 354 // carrat. 355 nextHit = layout.getVisualOtherHit(nextHit); 356 } 357 pos = nextHit.getInsertionIndex() + startOffset; 358 359 if(pos == endOffset) { 360 // A move to the left from an internal position will 361 // only take us to the endOffset in a right to left run. 362 text = v.getText(endOffset - 1, endOffset); 363 char c = text.array[text.offset]; 364 SegmentCache.releaseSharedSegment(text); 365 if(c == '\n') { 366 return -1; 367 } 368 biasRet[0] = Position.Bias.Backward; 369 } 370 else { 371 biasRet[0] = Position.Bias.Forward; 372 } 373 return pos; 374 default: 375 throw new IllegalArgumentException("Bad direction: " + direction); 376 } 377 return pos; 378 379 } 380 // --- variables --------------------------------------------- 381 382 TextLayout layout; 383 384 } --- EOF ---