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