1. /*
  2. * @(#)TextMeasurer.java 1.22 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * (C) Copyright Taligent, Inc. 1996 - 1997, All Rights Reserved
  9. * (C) Copyright IBM Corp. 1996 - 1998, All Rights Reserved
  10. *
  11. * The original version of this source code and documentation is
  12. * copyrighted and owned by Taligent, Inc., a wholly-owned subsidiary
  13. * of IBM. These materials are provided under terms of a License
  14. * Agreement between Taligent and Sun. This technology is protected
  15. * by multiple US and International patents.
  16. *
  17. * This notice and attribution to Taligent may not be removed.
  18. * Taligent is a registered trademark of Taligent, Inc.
  19. *
  20. */
  21. package java.awt.font;
  22. import java.awt.Font;
  23. import java.text.CharacterIterator;
  24. import java.text.AttributedCharacterIterator;
  25. import java.text.AttributedString;
  26. import java.awt.font.FontRenderContext;
  27. import sun.awt.font.TextLineComponent;
  28. import sun.awt.font.TextLabelFactory;
  29. import sun.awt.font.Bidi;
  30. import java.util.Map;
  31. /**
  32. * This class implements the 'primitive' operations needed for line
  33. * break: measuring up to a given advance, determining the advance of
  34. * a range of characters, and generating a TextLayout for a range of
  35. * characters. It also provides optimizations for incremental editing
  36. * of paragraphs.
  37. *
  38. * Most clients will use the more convenient LineBreakMeasurer, which
  39. * implements the standard line break policy (placing as many words as
  40. * will fit on each line).
  41. *
  42. * @see LineBreakMeasurer
  43. */
  44. class TextMeasurer {
  45. private FontRenderContext fFrc;
  46. private int fStart;
  47. // characters in source text
  48. private char[] fChars;
  49. // Bidi for this paragraph
  50. private Bidi fBidi;
  51. // Levels array for chars in this paragraph - needed to reorder
  52. // trailing counterdirectional whitespace
  53. private byte[] fLevels;
  54. // glyph arrays in logical order
  55. private TextLineComponent[] fComponents;
  56. // paragraph data - same across all layouts
  57. private boolean fIsDirectionLTR;
  58. private byte fBaseline;
  59. private float[] fBaselineOffsets;
  60. private float fJustifyRatio = 1;
  61. /**
  62. * Construct a TextMeasurer from the source text. The source text
  63. * should be a single entire paragraph.
  64. * @param text the source paragraph
  65. */
  66. public TextMeasurer(AttributedCharacterIterator text, FontRenderContext frc) {
  67. fFrc = frc;
  68. initSelf(text);
  69. }
  70. private void initSelf(AttributedCharacterIterator text) {
  71. fStart = text.getBeginIndex();
  72. // extract chars
  73. fChars = new char[text.getEndIndex() - fStart];
  74. int n = 0;
  75. for (char c = text.first(); c != text.DONE; c = text.next()) {
  76. fChars[n++] = c;
  77. }
  78. TextLine.FontSource fontSource = new TextLine.ACIFontSource(text);
  79. fIsDirectionLTR = true;
  80. boolean requiresBidi = false;
  81. boolean directionKnown = false;
  82. byte[] embs = null;
  83. text.first();
  84. Map attributes = text.getAttributes();
  85. if (attributes != null) {
  86. try {
  87. Boolean runDirection = (Boolean)attributes.get(TextAttribute.RUN_DIRECTION);
  88. if (runDirection != null) {
  89. directionKnown = true;
  90. fIsDirectionLTR = TextAttribute.RUN_DIRECTION_LTR.equals(runDirection);
  91. requiresBidi = !fIsDirectionLTR;
  92. }
  93. }
  94. catch (ClassCastException e) {
  95. }
  96. try {
  97. Float justifyLF = (Float)attributes.get(TextAttribute.JUSTIFICATION);
  98. if (justifyLF != null) {
  99. fJustifyRatio = justifyLF.floatValue();
  100. if (fJustifyRatio < 0) {
  101. fJustifyRatio = 0;
  102. } else if (fJustifyRatio > 1) {
  103. fJustifyRatio = 1;
  104. }
  105. }
  106. }
  107. catch (ClassCastException e) {
  108. }
  109. }
  110. int begin = text.getBeginIndex();
  111. int end = text.getEndIndex();
  112. int pos = begin;
  113. byte level = 0;
  114. byte baselevel = (byte)((directionKnown && !fIsDirectionLTR) ? 1 : 0);
  115. do {
  116. text.setIndex(pos);
  117. attributes = text.getAttributes();
  118. Object embeddingLevel = attributes.get(TextAttribute.BIDI_EMBEDDING);
  119. int newpos = text.getRunLimit(TextAttribute.BIDI_EMBEDDING);
  120. if (embeddingLevel != null) {
  121. try {
  122. int intLevel = ((Integer)embeddingLevel).intValue();
  123. if (intLevel >= -15 && intLevel < 16) {
  124. level = (byte)intLevel;
  125. if (embs == null) {
  126. embs = new byte[fChars.length];
  127. requiresBidi = true;
  128. if (!directionKnown) {
  129. directionKnown = true;
  130. fIsDirectionLTR = Bidi.defaultIsLTR(fChars, 0, fChars.length);
  131. baselevel = (byte)(fIsDirectionLTR ? 0 : 1);
  132. }
  133. if (!fIsDirectionLTR) {
  134. for (int i = 0; i < pos - begin; ++i) {
  135. embs[i] = baselevel; // set initial level if rtl, already 0 so ok if ltr
  136. }
  137. }
  138. }
  139. }
  140. }
  141. catch (ClassCastException e) {
  142. }
  143. } else {
  144. if (embs != null) {
  145. level = baselevel;
  146. }
  147. }
  148. if (embs != null && level != 0) {
  149. for (int i = pos - begin; i < newpos - begin; ++i) {
  150. embs[i] = level;
  151. }
  152. }
  153. pos = newpos;
  154. } while (pos < end);
  155. if (!requiresBidi) {
  156. for (int i = 0; i < fChars.length; i++) {
  157. if (Bidi.requiresBidi(fChars[i])) {
  158. requiresBidi = true;
  159. break;
  160. }
  161. }
  162. }
  163. if (requiresBidi) {
  164. if (!directionKnown) {
  165. fIsDirectionLTR = Bidi.defaultIsLTR(fChars, 0, fChars.length);
  166. }
  167. if (embs == null) {
  168. embs = Bidi.getEmbeddingArray(fChars, fIsDirectionLTR);
  169. }
  170. fBidi = new Bidi(fChars, embs, fIsDirectionLTR);
  171. }
  172. TextLabelFactory factory = new TextLabelFactory(fFrc, fChars, fBidi);
  173. int[] charsLtoV = null;
  174. fLevels = null;
  175. if (fBidi != null) {
  176. charsLtoV = fBidi.getLogicalToVisualMap();
  177. fLevels = fBidi.getLevels();
  178. }
  179. fComponents = TextLine.getComponents(
  180. fontSource, fChars, 0, fChars.length, charsLtoV, fLevels, factory);
  181. Font firstFont = fontSource.fontAt(0);
  182. if (firstFont == null) {
  183. firstFont = fontSource.getBestFontAt(0);
  184. }
  185. LineMetrics lm = firstFont.getLineMetrics(fChars, 0, 1, fFrc);
  186. fBaselineOffsets = lm.getBaselineOffsets();
  187. }
  188. private int calcLineBreak(int startPos, float width) {
  189. int tlcIndex;
  190. int tlcStart = 0;
  191. for (tlcIndex = 0; tlcIndex < fComponents.length; tlcIndex++) {
  192. int gaLimit = tlcStart + fComponents[tlcIndex].getNumCharacters();
  193. if (gaLimit > startPos) {
  194. break;
  195. }
  196. else {
  197. tlcStart = gaLimit;
  198. }
  199. }
  200. // tlcStart is now the start of the tlc at tlcIndex
  201. for (; tlcIndex < fComponents.length; tlcIndex++) {
  202. TextLineComponent tlc = fComponents[tlcIndex];
  203. int numCharsInGa = tlc.getNumCharacters();
  204. int lineBreak = tlc.getLineBreakIndex(startPos - tlcStart, width);
  205. if (lineBreak == numCharsInGa) {
  206. width -= tlc.getAdvanceBetween(startPos - tlcStart, lineBreak);
  207. tlcStart += numCharsInGa;
  208. startPos = tlcStart;
  209. }
  210. else {
  211. return tlcStart + lineBreak;
  212. }
  213. }
  214. return fChars.length;
  215. }
  216. /**
  217. * According to the Unicode Bidirectional Behavior specification
  218. * (Unicode Standard 2.0, section 3.11), whitespace at the ends
  219. * of lines which would naturally flow against the base direction
  220. * must be made to flow with the line direction, and moved to the
  221. * end of the line. This method returns the start of the sequence
  222. * of trailing whitespace characters to move to the end of a
  223. * line taken from the given range.
  224. */
  225. private int trailingCdWhitespaceStart(int startPos, int limitPos) {
  226. int cdWsStart = limitPos;
  227. if (fLevels != null) {
  228. // Back up over counterdirectional whitespace
  229. final byte baseLevel = (byte) (fIsDirectionLTR? 0 : 1);
  230. for (cdWsStart = limitPos-1; cdWsStart >= startPos; cdWsStart--) {
  231. if ((fLevels[cdWsStart] % 2) == baseLevel ||
  232. Bidi.getDirectionCode(fChars[cdWsStart]) != Bidi.WS) {
  233. cdWsStart++;
  234. break;
  235. }
  236. }
  237. }
  238. return cdWsStart;
  239. }
  240. private TextLineComponent[] makeComponentsOnRange(int startPos,
  241. int limitPos) {
  242. // sigh I really hate to do this here since it's part of the
  243. // bidi algorithm.
  244. // cdWsStart is the start of the trailing counterdirectional
  245. // whitespace
  246. final int cdWsStart = trailingCdWhitespaceStart(startPos, limitPos);
  247. int tlcIndex;
  248. int tlcStart = 0;
  249. for (tlcIndex = 0; tlcIndex < fComponents.length; tlcIndex++) {
  250. int gaLimit = tlcStart + fComponents[tlcIndex].getNumCharacters();
  251. if (gaLimit > startPos) {
  252. break;
  253. }
  254. else {
  255. tlcStart = gaLimit;
  256. }
  257. }
  258. // tlcStart is now the start of the tlc at tlcIndex
  259. int componentCount;
  260. {
  261. boolean split = false;
  262. int compStart = tlcStart;
  263. int lim=tlcIndex;
  264. for (boolean cont=true; cont; lim++) {
  265. int gaLimit = compStart + fComponents[lim].getNumCharacters();
  266. if (cdWsStart > Math.max(compStart, startPos)
  267. && cdWsStart < Math.min(gaLimit, limitPos)) {
  268. split = true;
  269. }
  270. if (gaLimit >= limitPos) {
  271. cont=false;
  272. }
  273. else {
  274. compStart = gaLimit;
  275. }
  276. }
  277. componentCount = lim-tlcIndex;
  278. if (split) {
  279. componentCount++;
  280. }
  281. }
  282. TextLineComponent[] components = new TextLineComponent[componentCount];
  283. int newCompIndex = 0;
  284. int linePos = startPos;
  285. int breakPt = cdWsStart;
  286. int subsetFlag;
  287. if (breakPt == startPos) {
  288. subsetFlag = fIsDirectionLTR? TextLineComponent.LEFT_TO_RIGHT :
  289. TextLineComponent.RIGHT_TO_LEFT;
  290. breakPt = limitPos;
  291. }
  292. else {
  293. subsetFlag = TextLineComponent.UNCHANGED;
  294. }
  295. while (linePos < limitPos) {
  296. int compLength = fComponents[tlcIndex].getNumCharacters();
  297. int tlcLimit = tlcStart + compLength;
  298. int start = Math.max(linePos, tlcStart);
  299. int limit = Math.min(breakPt, tlcLimit);
  300. components[newCompIndex++] = fComponents[tlcIndex].getSubset(
  301. start-tlcStart,
  302. limit-tlcStart,
  303. subsetFlag);
  304. linePos += (limit-start);
  305. if (linePos == breakPt) {
  306. breakPt = limitPos;
  307. subsetFlag = fIsDirectionLTR? TextLineComponent.LEFT_TO_RIGHT :
  308. TextLineComponent.RIGHT_TO_LEFT;
  309. }
  310. if (linePos == tlcLimit) {
  311. tlcIndex++;
  312. tlcStart = tlcLimit;
  313. }
  314. }
  315. return components;
  316. }
  317. private TextLine makeTextLineOnRange(int startPos, int limitPos) {
  318. int[] charsLtoV = null;
  319. byte[] charLevels = null;
  320. if (fBidi != null) {
  321. Bidi lineBidi = fBidi.createLineBidi(startPos, limitPos);
  322. charsLtoV = lineBidi.getLogicalToVisualMap();
  323. charLevels = lineBidi.getLevels();
  324. }
  325. TextLineComponent[] components = makeComponentsOnRange(startPos, limitPos);
  326. return new TextLine(components,
  327. fBaselineOffsets,
  328. fChars,
  329. startPos,
  330. limitPos,
  331. charsLtoV,
  332. charLevels,
  333. fIsDirectionLTR);
  334. }
  335. /**
  336. * Accumulate the advances of the characters at and after start,
  337. * until a character is reached whose advance would equal or
  338. * exceed maxAdvance. Return the index of that character.
  339. * @param start the character index at which to start measuring.
  340. * This is the absolute index, not the relative index within the
  341. * paragraph.
  342. * @param maxAdvance the maximumAdvance
  343. * @return the index of the character whose advance exceeded
  344. * maxAdvance.
  345. */
  346. public int getLineBreakIndex(int start, float maxAdvance) {
  347. return calcLineBreak(start - fStart, maxAdvance) + fStart;
  348. }
  349. /**
  350. * Return the sum of the advances for the characters
  351. * from start up to limit.
  352. *
  353. * @param start the character index at which to start measuring
  354. * @param limit the character index at which to stop measuring
  355. * @return the sum of the advances of the range of characters.
  356. */
  357. public float getAdvanceBetween(int start, int limit) {
  358. TextLine line = makeTextLineOnRange(start - fStart, limit - fStart);
  359. return line.getMetrics().advance;
  360. // could cache line in case getLayout is called with same start, limit
  361. }
  362. /**
  363. * Return a layout that represents the characters. The number
  364. * of characters must be >= 1. The returned layout will apply the
  365. * bidi 'line reordering' rules to the text.
  366. *
  367. * @param start the index of the first character to use
  368. * @param limit the index past the last character to use
  369. * @return a new layout
  370. */
  371. public TextLayout getLayout(int start, int limit) {
  372. TextLine textLine = makeTextLineOnRange(start-fStart, limit-fStart);
  373. return new TextLayout(
  374. textLine, fBaseline, fBaselineOffsets, fJustifyRatio);
  375. }
  376. /**
  377. * An optimization to facilitate inserting single characters from
  378. * a paragraph. Clients can then remeasure and reextract layouts as
  379. * required. In general, clients can always begin remeasuring from
  380. * the layout preceeding the one that contains the new character,
  381. * and stop measuring once the start of a subsequent layout matches
  382. * up with the start of a layout the client has cached (plus one
  383. * for the inserted character).
  384. *
  385. * @param newParagraph the text of the paragraph after performing
  386. * the insertion.
  387. * @param insertPos the position in the text
  388. * at which the single character was inserted.
  389. * @see #deleteChar
  390. */
  391. public void insertChar(AttributedCharacterIterator newParagraph, int insertPos) {
  392. initSelf(newParagraph);
  393. }
  394. /**
  395. * An optimization to facilitate deleting single characters from
  396. * a paragraph.
  397. *
  398. * @param newParagraph the text of the paragraph after performing
  399. * the deletion.
  400. * @param deletePos the position in the text
  401. * at which the single character was deleted.
  402. * @see #insertChar
  403. */
  404. public void deleteChar(AttributedCharacterIterator newParagraph, int deletePos) {
  405. initSelf(newParagraph);
  406. }
  407. }