1. /*
  2. * @(#)TextLayoutStrategy.java 1.22 04/05/05
  3. *
  4. * Copyright 2004 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.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.22 05/05/04
  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. Container container = fv.getContainer();
  255. FontRenderContext frc = com.sun.java.swing.SwingUtilities2.
  256. getFontRenderContext(container);
  257. BreakIterator iter;
  258. Container c = fv.getContainer();
  259. if (c != null) {
  260. iter = BreakIterator.getLineInstance(c.getLocale());
  261. } else {
  262. iter = BreakIterator.getLineInstance();
  263. }
  264. measurer = new LineBreakMeasurer(text, iter, frc);
  265. // If the children of the FlowView's logical view are GlyphViews, they
  266. // need to have their painters updated.
  267. int n = lv.getViewCount();
  268. for( int i=0; i<n; i++ ) {
  269. View child = lv.getView(i);
  270. if( child instanceof GlyphView ) {
  271. int p0 = child.getStartOffset();
  272. int p1 = child.getEndOffset();
  273. measurer.setPosition(text.toIteratorIndex(p0));
  274. TextLayout layout
  275. = measurer.nextLayout( Float.MAX_VALUE,
  276. text.toIteratorIndex(p1), false );
  277. ((GlyphView)child).setGlyphPainter(new GlyphPainter2(layout));
  278. }
  279. }
  280. // Reset measurer.
  281. measurer.setPosition(text.getBeginIndex());
  282. }
  283. // --- variables -------------------------------------------------------
  284. private LineBreakMeasurer measurer;
  285. private AttributedSegment text;
  286. /**
  287. * Implementation of AttributedCharacterIterator that supports
  288. * the GlyphView attributes for rendering the glyphs through a
  289. * TextLayout.
  290. */
  291. static class AttributedSegment extends Segment implements AttributedCharacterIterator {
  292. AttributedSegment() {
  293. }
  294. View getView() {
  295. return v;
  296. }
  297. void setView(View v) {
  298. this.v = v;
  299. Document doc = v.getDocument();
  300. int p0 = v.getStartOffset();
  301. int p1 = v.getEndOffset();
  302. try {
  303. doc.getText(p0, p1 - p0, this);
  304. } catch (BadLocationException bl) {
  305. throw new IllegalArgumentException("Invalid view");
  306. }
  307. first();
  308. }
  309. /**
  310. * Get a boundary position for the font.
  311. * This is implemented to assume that two fonts are
  312. * equal if their references are equal (i.e. that the
  313. * font came from a cache).
  314. *
  315. * @return the location in model coordinates. This is
  316. * not the same as the Segment coordinates.
  317. */
  318. int getFontBoundary(int childIndex, int dir) {
  319. View child = v.getView(childIndex);
  320. Font f = getFont(childIndex);
  321. for (childIndex += dir; (childIndex >= 0) && (childIndex < v.getViewCount());
  322. childIndex += dir) {
  323. Font next = getFont(childIndex);
  324. if (next != f) {
  325. // this run is different
  326. break;
  327. }
  328. child = v.getView(childIndex);
  329. }
  330. return (dir < 0) ? child.getStartOffset() : child.getEndOffset();
  331. }
  332. /**
  333. * Get the font at the given child index.
  334. */
  335. Font getFont(int childIndex) {
  336. View child = v.getView(childIndex);
  337. if (child instanceof GlyphView) {
  338. return ((GlyphView)child).getFont();
  339. }
  340. return null;
  341. }
  342. int toModelPosition(int index) {
  343. return v.getStartOffset() + (index - getBeginIndex());
  344. }
  345. int toIteratorIndex(int pos) {
  346. return pos - v.getStartOffset() + getBeginIndex();
  347. }
  348. // --- AttributedCharacterIterator methods -------------------------
  349. /**
  350. * Returns the index of the first character of the run
  351. * with respect to all attributes containing the current character.
  352. */
  353. public int getRunStart() {
  354. int pos = toModelPosition(getIndex());
  355. int i = v.getViewIndex(pos, Position.Bias.Forward);
  356. View child = v.getView(i);
  357. return toIteratorIndex(child.getStartOffset());
  358. }
  359. /**
  360. * Returns the index of the first character of the run
  361. * with respect to the given attribute containing the current character.
  362. */
  363. public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
  364. if (attribute instanceof TextAttribute) {
  365. int pos = toModelPosition(getIndex());
  366. int i = v.getViewIndex(pos, Position.Bias.Forward);
  367. if (attribute == TextAttribute.FONT) {
  368. return toIteratorIndex(getFontBoundary(i, -1));
  369. }
  370. }
  371. return getBeginIndex();
  372. }
  373. /**
  374. * Returns the index of the first character of the run
  375. * with respect to the given attributes containing the current character.
  376. */
  377. public int getRunStart(Set<? extends Attribute> attributes) {
  378. int index = getBeginIndex();
  379. Object[] a = attributes.toArray();
  380. for (int i = 0; i < a.length; i++) {
  381. TextAttribute attr = (TextAttribute) a[i];
  382. index = Math.max(getRunStart(attr), index);
  383. }
  384. return Math.min(getIndex(), index);
  385. }
  386. /**
  387. * Returns the index of the first character following the run
  388. * with respect to all attributes containing the current character.
  389. */
  390. public int getRunLimit() {
  391. int pos = toModelPosition(getIndex());
  392. int i = v.getViewIndex(pos, Position.Bias.Forward);
  393. View child = v.getView(i);
  394. return toIteratorIndex(child.getEndOffset());
  395. }
  396. /**
  397. * Returns the index of the first character following the run
  398. * with respect to the given attribute containing the current character.
  399. */
  400. public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
  401. if (attribute instanceof TextAttribute) {
  402. int pos = toModelPosition(getIndex());
  403. int i = v.getViewIndex(pos, Position.Bias.Forward);
  404. if (attribute == TextAttribute.FONT) {
  405. return toIteratorIndex(getFontBoundary(i, 1));
  406. }
  407. }
  408. return getEndIndex();
  409. }
  410. /**
  411. * Returns the index of the first character following the run
  412. * with respect to the given attributes containing the current character.
  413. */
  414. public int getRunLimit(Set<? extends Attribute> attributes) {
  415. int index = getEndIndex();
  416. Object[] a = attributes.toArray();
  417. for (int i = 0; i < a.length; i++) {
  418. TextAttribute attr = (TextAttribute) a[i];
  419. index = Math.min(getRunLimit(attr), index);
  420. }
  421. return Math.max(getIndex(), index);
  422. }
  423. /**
  424. * Returns a map with the attributes defined on the current
  425. * character.
  426. */
  427. public Map getAttributes() {
  428. Object[] ka = keys.toArray();
  429. Hashtable h = new Hashtable();
  430. for (int i = 0; i < ka.length; i++) {
  431. TextAttribute a = (TextAttribute) ka[i];
  432. Object value = getAttribute(a);
  433. if (value != null) {
  434. h.put(a, value);
  435. }
  436. }
  437. return h;
  438. }
  439. /**
  440. * Returns the value of the named attribute for the current character.
  441. * Returns null if the attribute is not defined.
  442. * @param attribute the key of the attribute whose value is requested.
  443. */
  444. public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
  445. int pos = toModelPosition(getIndex());
  446. int childIndex = v.getViewIndex(pos, Position.Bias.Forward);
  447. if (attribute == TextAttribute.FONT) {
  448. return getFont(childIndex);
  449. } else if( attribute == TextAttribute.RUN_DIRECTION ) {
  450. return
  451. v.getDocument().getProperty(TextAttribute.RUN_DIRECTION);
  452. }
  453. return null;
  454. }
  455. /**
  456. * Returns the keys of all attributes defined on the
  457. * iterator's text range. The set is empty if no
  458. * attributes are defined.
  459. */
  460. public Set getAllAttributeKeys() {
  461. return keys;
  462. }
  463. View v;
  464. static Set keys;
  465. static {
  466. keys = new HashSet();
  467. keys.add(TextAttribute.FONT);
  468. keys.add(TextAttribute.RUN_DIRECTION);
  469. }
  470. }
  471. }