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