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