1. /*
  2. * @(#)TextLine.java 1.52 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
  9. *
  10. */
  11. package java.awt.font;
  12. import java.awt.Font;
  13. import java.awt.Graphics2D;
  14. import java.awt.Shape;
  15. import java.awt.geom.Rectangle2D;
  16. import java.awt.geom.AffineTransform;
  17. import java.awt.geom.GeneralPath;
  18. import java.awt.im.InputMethodHighlight;
  19. import java.text.CharacterIterator;
  20. import java.text.AttributedCharacterIterator;
  21. import java.text.Annotation;
  22. import java.text.Bidi;
  23. import java.util.Map;
  24. import java.util.Hashtable;
  25. import sun.font.BidiUtils;
  26. import sun.font.CoreMetrics;
  27. import sun.font.Decoration;
  28. import sun.font.FontLineMetrics;
  29. import sun.font.FontResolver;
  30. import sun.font.GraphicComponent;
  31. import sun.font.TextLabelFactory;
  32. import sun.font.TextLineComponent;
  33. import sun.text.CodePointIterator;
  34. final class TextLine {
  35. static final class TextLineMetrics {
  36. public final float ascent;
  37. public final float descent;
  38. public final float leading;
  39. public final float advance;
  40. public TextLineMetrics(float ascent,
  41. float descent,
  42. float leading,
  43. float advance) {
  44. this.ascent = ascent;
  45. this.descent = descent;
  46. this.leading = leading;
  47. this.advance = advance;
  48. }
  49. }
  50. private TextLineComponent[] fComponents;
  51. private float[] fBaselineOffsets;
  52. private int[] fComponentVisualOrder; // if null, ltr
  53. private float[] locs; // x,y pairs for components in visual order
  54. private char[] fChars;
  55. private int fCharsStart;
  56. private int fCharsLimit;
  57. private int[] fCharVisualOrder; // if null, ltr
  58. private int[] fCharLogicalOrder; // if null, ltr
  59. private byte[] fCharLevels; // if null, 0
  60. private boolean fIsDirectionLTR;
  61. private TextLineMetrics fMetrics = null; // built on demand in getMetrics
  62. public TextLine(TextLineComponent[] components,
  63. float[] baselineOffsets,
  64. char[] chars,
  65. int charsStart,
  66. int charsLimit,
  67. int[] charLogicalOrder,
  68. byte[] charLevels,
  69. boolean isDirectionLTR) {
  70. int[] componentVisualOrder = computeComponentOrder(components,
  71. charLogicalOrder);
  72. fComponents = components;
  73. fBaselineOffsets = baselineOffsets;
  74. fComponentVisualOrder = componentVisualOrder;
  75. fChars = chars;
  76. fCharsStart = charsStart;
  77. fCharsLimit = charsLimit;
  78. fCharLogicalOrder = charLogicalOrder;
  79. fCharLevels = charLevels;
  80. fIsDirectionLTR = isDirectionLTR;
  81. checkCtorArgs();
  82. init();
  83. }
  84. private void checkCtorArgs() {
  85. int checkCharCount = 0;
  86. for (int i=0; i < fComponents.length; i++) {
  87. checkCharCount += fComponents[i].getNumCharacters();
  88. }
  89. if (checkCharCount != this.characterCount()) {
  90. throw new IllegalArgumentException("Invalid TextLine! " +
  91. "char count is different from " +
  92. "sum of char counts of components.");
  93. }
  94. }
  95. private void init() {
  96. // first, we need to check for graphic components on the TOP or BOTTOM baselines. So
  97. // we perform the work that used to be in getMetrics here.
  98. float ascent = 0;
  99. float descent = 0;
  100. float leading = 0;
  101. float advance = 0;
  102. // ascent + descent must not be less than this value
  103. float maxGraphicHeight = 0;
  104. float maxGraphicHeightWithLeading = 0;
  105. // walk through EGA's
  106. TextLineComponent tlc;
  107. boolean fitTopAndBottomGraphics = false;
  108. for (int i = 0; i < fComponents.length; i++) {
  109. tlc = fComponents[i];
  110. CoreMetrics cm = tlc.getCoreMetrics();
  111. byte baseline = (byte)cm.baselineIndex;
  112. if (baseline >= 0) {
  113. float baselineOffset = fBaselineOffsets[baseline];
  114. ascent = Math.max(ascent, -baselineOffset + cm.ascent);
  115. float gd = baselineOffset + cm.descent;
  116. descent = Math.max(descent, gd);
  117. leading = Math.max(leading, gd + cm.leading);
  118. }
  119. else {
  120. fitTopAndBottomGraphics = true;
  121. float graphicHeight = cm.ascent + cm.descent;
  122. float graphicHeightWithLeading = graphicHeight + cm.leading;
  123. maxGraphicHeight = Math.max(maxGraphicHeight, graphicHeight);
  124. maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading,
  125. graphicHeightWithLeading);
  126. }
  127. }
  128. if (fitTopAndBottomGraphics) {
  129. if (maxGraphicHeight > ascent + descent) {
  130. descent = maxGraphicHeight - ascent;
  131. }
  132. if (maxGraphicHeightWithLeading > ascent + leading) {
  133. leading = maxGraphicHeightWithLeading - ascent;
  134. }
  135. }
  136. leading -= descent;
  137. // we now know enough to compute the locs, but we need the final loc
  138. // for the advance before we can create the metrics object
  139. if (fitTopAndBottomGraphics) {
  140. // we have top or bottom baselines, so expand the baselines array
  141. // full offsets are needed by CoreMetrics.effectiveBaselineOffset
  142. fBaselineOffsets = new float[] {
  143. fBaselineOffsets[0],
  144. fBaselineOffsets[1],
  145. fBaselineOffsets[2],
  146. descent,
  147. -ascent
  148. };
  149. }
  150. float x = 0;
  151. float y = 0;
  152. CoreMetrics pcm = null;
  153. locs = new float[fComponents.length * 2 + 2];
  154. for (int i = 0, n = 0; i < fComponents.length; ++i, n += 2) {
  155. int vi = fComponentVisualOrder == null ? i : fComponentVisualOrder[i];
  156. tlc = fComponents[vi];
  157. CoreMetrics cm = tlc.getCoreMetrics();
  158. if ((pcm != null) &&
  159. (pcm.italicAngle != 0 || cm.italicAngle != 0) && // adjust because of italics
  160. (pcm.italicAngle != cm.italicAngle ||
  161. pcm.baselineIndex != cm.baselineIndex ||
  162. pcm.ssOffset != cm.ssOffset)) {
  163. // 1) compute the area of overlap - min effective ascent and min effective descent
  164. // 2) compute the x positions along italic angle of ascent and descent for left and right
  165. // 3) compute maximum left - right, adjust right position by this value
  166. // this is a crude form of kerning between textcomponents
  167. // 1)
  168. float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
  169. float pa = pb - pcm.ascent;
  170. float pd = pb + pcm.descent;
  171. pb += pcm.ssOffset;
  172. float cb = cm.effectiveBaselineOffset(fBaselineOffsets);
  173. float ca = cb - cm.ascent;
  174. float cd = cb + cm.descent;
  175. cb += cm.ssOffset;
  176. float a = Math.max(pa, ca);
  177. float d = Math.min(pd, cd);
  178. // 2)
  179. float pax = pcm.italicAngle * (pb - a);
  180. float pdx = pcm.italicAngle * (pb - d);
  181. float cax = cm.italicAngle * (cb - a);
  182. float cdx = cm.italicAngle * (cb - d);
  183. // 3)
  184. float dax = pax - cax;
  185. float ddx = pdx - cdx;
  186. float dx = Math.max(dax, ddx);
  187. x += dx;
  188. y = cb;
  189. } else {
  190. // no italic adjustment for x, but still need to compute y
  191. y = cm.effectiveBaselineOffset(fBaselineOffsets) + cm.ssOffset;
  192. }
  193. locs[n] = x;
  194. locs[n+1] = y;
  195. x += tlc.getAdvance();
  196. pcm = cm;
  197. }
  198. // do we want italic padding at the right of the line?
  199. if (pcm.italicAngle != 0) {
  200. float pb = pcm.effectiveBaselineOffset(fBaselineOffsets);
  201. float pa = pb - pcm.ascent;
  202. float pd = pb + pcm.descent;
  203. pb += pcm.ssOffset;
  204. float d;
  205. if (pcm.italicAngle > 0) {
  206. d = pb + pcm.ascent;
  207. } else {
  208. d = pb - pcm.descent;
  209. }
  210. d *= pcm.italicAngle;
  211. x += d;
  212. }
  213. locs[locs.length - 2] = x;
  214. // locs[locs.length - 1] = 0; // final offset is always back on baseline
  215. // ok, build fMetrics since we have the final advance
  216. advance = x;
  217. fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
  218. }
  219. private abstract static class Function {
  220. abstract float computeFunction(TextLine line,
  221. int componentIndex,
  222. int indexInArray);
  223. }
  224. private static Function fgPosAdvF = new Function() {
  225. float computeFunction(TextLine line,
  226. int componentIndex,
  227. int indexInArray) {
  228. TextLineComponent tlc = line.fComponents[componentIndex];
  229. int vi = line.fComponentVisualOrder == null
  230. ? componentIndex
  231. : line.fComponentVisualOrder[componentIndex];
  232. return line.locs[vi * 2] + tlc.getCharX(indexInArray) + tlc.getCharAdvance(indexInArray);
  233. }
  234. };
  235. private static Function fgAdvanceF = new Function() {
  236. float computeFunction(TextLine line,
  237. int componentIndex,
  238. int indexInArray) {
  239. TextLineComponent tlc = line.fComponents[componentIndex];
  240. return tlc.getCharAdvance(indexInArray);
  241. }
  242. };
  243. private static Function fgXPositionF = new Function() {
  244. float computeFunction(TextLine line,
  245. int componentIndex,
  246. int indexInArray) {
  247. int vi = line.fComponentVisualOrder == null
  248. ? componentIndex
  249. : line.fComponentVisualOrder[componentIndex];
  250. TextLineComponent tlc = line.fComponents[componentIndex];
  251. return line.locs[vi * 2] + tlc.getCharX(indexInArray);
  252. }
  253. };
  254. private static Function fgYPositionF = new Function() {
  255. float computeFunction(TextLine line,
  256. int componentIndex,
  257. int indexInArray) {
  258. TextLineComponent tlc = line.fComponents[componentIndex];
  259. float charPos = tlc.getCharY(indexInArray);
  260. // charPos is relative to the component - adjust for
  261. // baseline
  262. return charPos + line.getComponentShift(componentIndex);
  263. }
  264. };
  265. public int characterCount() {
  266. return fCharsLimit - fCharsStart;
  267. }
  268. public boolean isDirectionLTR() {
  269. return fIsDirectionLTR;
  270. }
  271. public TextLineMetrics getMetrics() {
  272. return fMetrics;
  273. }
  274. public int visualToLogical(int visualIndex) {
  275. if (fCharLogicalOrder == null) {
  276. return visualIndex;
  277. }
  278. if (fCharVisualOrder == null) {
  279. fCharVisualOrder = BidiUtils.createInverseMap(fCharLogicalOrder);
  280. }
  281. return fCharVisualOrder[visualIndex];
  282. }
  283. public int logicalToVisual(int logicalIndex) {
  284. return (fCharLogicalOrder == null)?
  285. logicalIndex : fCharLogicalOrder[logicalIndex];
  286. }
  287. public byte getCharLevel(int logicalIndex) {
  288. return fCharLevels==null? 0 : fCharLevels[logicalIndex];
  289. }
  290. public boolean isCharLTR(int logicalIndex) {
  291. return (getCharLevel(logicalIndex) & 0x1) == 0;
  292. }
  293. public int getCharType(int logicalIndex) {
  294. return Character.getType(fChars[logicalIndex + fCharsStart]);
  295. }
  296. public boolean isCharSpace(int logicalIndex) {
  297. return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]);
  298. }
  299. public boolean isCharWhitespace(int logicalIndex) {
  300. return Character.isWhitespace(fChars[logicalIndex + fCharsStart]);
  301. }
  302. public float getCharAngle(int logicalIndex) {
  303. return getCoreMetricsAt(logicalIndex).italicAngle;
  304. }
  305. public CoreMetrics getCoreMetricsAt(int logicalIndex) {
  306. if (logicalIndex < 0) {
  307. throw new IllegalArgumentException("Negative logicalIndex.");
  308. }
  309. if (logicalIndex > fCharsLimit - fCharsStart) {
  310. throw new IllegalArgumentException("logicalIndex too large.");
  311. }
  312. int currentTlc = 0;
  313. int tlcStart = 0;
  314. int tlcLimit = 0;
  315. do {
  316. tlcLimit += fComponents[currentTlc].getNumCharacters();
  317. if (tlcLimit > logicalIndex) {
  318. break;
  319. }
  320. ++currentTlc;
  321. tlcStart = tlcLimit;
  322. } while(currentTlc < fComponents.length);
  323. return fComponents[currentTlc].getCoreMetrics();
  324. }
  325. public float getCharAscent(int logicalIndex) {
  326. return getCoreMetricsAt(logicalIndex).ascent;
  327. }
  328. public float getCharDescent(int logicalIndex) {
  329. return getCoreMetricsAt(logicalIndex).descent;
  330. }
  331. public float getCharShift(int logicalIndex) {
  332. return getCoreMetricsAt(logicalIndex).ssOffset;
  333. }
  334. private float applyFunctionAtIndex(int logicalIndex, Function f) {
  335. if (logicalIndex < 0) {
  336. throw new IllegalArgumentException("Negative logicalIndex.");
  337. }
  338. int tlcStart = 0;
  339. for(int i=0; i < fComponents.length; i++) {
  340. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  341. if (tlcLimit > logicalIndex) {
  342. return f.computeFunction(this, i, logicalIndex - tlcStart);
  343. }
  344. else {
  345. tlcStart = tlcLimit;
  346. }
  347. }
  348. throw new IllegalArgumentException("logicalIndex too large.");
  349. }
  350. public float getCharAdvance(int logicalIndex) {
  351. return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
  352. }
  353. public float getCharXPosition(int logicalIndex) {
  354. return applyFunctionAtIndex(logicalIndex, fgXPositionF);
  355. }
  356. public float getCharYPosition(int logicalIndex) {
  357. return applyFunctionAtIndex(logicalIndex, fgYPositionF);
  358. }
  359. public float getCharLinePosition(int logicalIndex) {
  360. return getCharXPosition(logicalIndex);
  361. }
  362. public float getCharLinePosition(int logicalIndex, boolean leading) {
  363. Function f = isCharLTR(logicalIndex) == leading ? fgXPositionF : fgPosAdvF;
  364. return applyFunctionAtIndex(logicalIndex, f);
  365. }
  366. public boolean caretAtOffsetIsValid(int offset) {
  367. if (offset < 0) {
  368. throw new IllegalArgumentException("Negative offset.");
  369. }
  370. int tlcStart = 0;
  371. for(int i=0; i < fComponents.length; i++) {
  372. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  373. if (tlcLimit > offset) {
  374. return fComponents[i].caretAtOffsetIsValid(offset-tlcStart);
  375. }
  376. else {
  377. tlcStart = tlcLimit;
  378. }
  379. }
  380. throw new IllegalArgumentException("logicalIndex too large.");
  381. }
  382. public Rectangle2D getCharBounds(int logicalIndex) {
  383. if (logicalIndex < 0) {
  384. throw new IllegalArgumentException("Negative logicalIndex.");
  385. }
  386. int tlcStart = 0;
  387. for (int i=0; i < fComponents.length; i++) {
  388. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  389. if (tlcLimit > logicalIndex) {
  390. TextLineComponent tlc = fComponents[i];
  391. int indexInTlc = logicalIndex - tlcStart;
  392. Rectangle2D chBounds = tlc.getCharVisualBounds(indexInTlc);
  393. int vi = fComponentVisualOrder == null ? i : fComponentVisualOrder[i];
  394. chBounds.setRect(chBounds.getX() + locs[vi * 2],
  395. chBounds.getY() + locs[vi * 2 + 1],
  396. chBounds.getWidth(),
  397. chBounds.getHeight());
  398. return chBounds;
  399. }
  400. else {
  401. tlcStart = tlcLimit;
  402. }
  403. }
  404. throw new IllegalArgumentException("logicalIndex too large.");
  405. }
  406. private float getComponentShift(int index) {
  407. CoreMetrics cm = fComponents[index].getCoreMetrics();
  408. return cm.effectiveBaselineOffset(fBaselineOffsets);
  409. }
  410. public void draw(Graphics2D g2, float x, float y) {
  411. for (int i = 0, n = 0; i < fComponents.length; i++, n += 2) {
  412. int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  413. TextLineComponent tlc = fComponents[vi];
  414. tlc.draw(g2, locs[n] + x, locs[n+1] + y);
  415. }
  416. }
  417. /** return the union of the visual bounds of all the components */
  418. public Rectangle2D getBounds() {
  419. float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
  420. float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
  421. for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
  422. int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  423. TextLineComponent tlc = fComponents[vi];
  424. Rectangle2D tlcBounds = tlc.getVisualBounds();
  425. float x = locs[n];
  426. float y = locs[n+1];
  427. left = Math.min(left, x + (float)tlcBounds.getX());
  428. right = Math.max(right, x + (float)tlcBounds.getMaxX());
  429. top = Math.min(top, y + (float)tlcBounds.getY());
  430. bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
  431. }
  432. return new Rectangle2D.Float(left, top, right-left, bottom-top);
  433. }
  434. public Rectangle2D getItalicBounds() {
  435. float left = Float.MAX_VALUE, right = -Float.MAX_VALUE;
  436. float top = Float.MAX_VALUE, bottom = -Float.MAX_VALUE;
  437. for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
  438. int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  439. TextLineComponent tlc = fComponents[vi];
  440. Rectangle2D tlcBounds = tlc.getItalicBounds();
  441. float x = locs[n];
  442. float y = locs[n+1];
  443. left = Math.min(left, x + (float)tlcBounds.getX());
  444. right = Math.max(right, x + (float)tlcBounds.getMaxX());
  445. top = Math.min(top, y + (float)tlcBounds.getY());
  446. bottom = Math.max(bottom, y + (float)tlcBounds.getMaxY());
  447. }
  448. return new Rectangle2D.Float(left, top, right-left, bottom-top);
  449. }
  450. public Shape getOutline(AffineTransform tx) {
  451. GeneralPath dstShape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
  452. for (int i=0, n = 0; i < fComponents.length; i++, n += 2) {
  453. int vi = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  454. TextLineComponent tlc = fComponents[vi];
  455. dstShape.append(tlc.getOutline(locs[n], locs[n+1]), false);
  456. }
  457. if (tx != null) {
  458. dstShape.transform(tx);
  459. }
  460. return dstShape;
  461. }
  462. public int hashCode() {
  463. return (fComponents.length << 16) ^
  464. (fComponents[0].hashCode() << 3) ^ (fCharsLimit-fCharsStart);
  465. }
  466. public String toString() {
  467. StringBuffer buf = new StringBuffer();
  468. for (int i = 0; i < fComponents.length; i++) {
  469. buf.append(fComponents[i]);
  470. }
  471. return buf.toString();
  472. }
  473. /**
  474. * Create a TextLine from the text. The Font must be able to
  475. * display all of the text.
  476. * attributes==null is equivalent to using an empty Map for
  477. * attributes
  478. */
  479. public static TextLine fastCreateTextLine(FontRenderContext frc,
  480. char[] chars,
  481. Font font,
  482. CoreMetrics lm,
  483. Map attributes) {
  484. boolean isDirectionLTR = true;
  485. byte[] levels = null;
  486. int[] charsLtoV = null;
  487. Bidi bidi = null;
  488. int characterCount = chars.length;
  489. boolean requiresBidi = false;
  490. boolean directionKnown = false;
  491. byte[] embs = null;
  492. if (attributes != null) {
  493. try {
  494. Boolean runDirection = (Boolean)attributes.get(TextAttribute.RUN_DIRECTION);
  495. if (runDirection != null) {
  496. directionKnown = true;
  497. isDirectionLTR = TextAttribute.RUN_DIRECTION_LTR.equals(runDirection);
  498. requiresBidi = !isDirectionLTR;
  499. }
  500. }
  501. catch (ClassCastException e) {
  502. }
  503. try {
  504. Integer embeddingLevel = (Integer)attributes.get(TextAttribute.BIDI_EMBEDDING);
  505. if (embeddingLevel != null) {
  506. int intLevel = embeddingLevel.intValue();
  507. if (intLevel >= -61 && intLevel < 62) {
  508. byte level = (byte)intLevel;
  509. requiresBidi = true;
  510. embs = new byte[characterCount];
  511. for (int i = 0; i < embs.length; ++i) {
  512. embs[i] = level;
  513. }
  514. }
  515. }
  516. }
  517. catch (ClassCastException e) {
  518. }
  519. }
  520. if (!requiresBidi) {
  521. requiresBidi = Bidi.requiresBidi(chars, 0, chars.length);
  522. }
  523. if (requiresBidi) {
  524. int bidiflags = Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT;
  525. if (directionKnown) {
  526. if (isDirectionLTR) {
  527. bidiflags = Bidi.DIRECTION_LEFT_TO_RIGHT;
  528. } else {
  529. bidiflags = Bidi.DIRECTION_RIGHT_TO_LEFT;
  530. }
  531. }
  532. bidi = new Bidi(chars, 0, embs, 0, chars.length, bidiflags);
  533. if (!bidi.isLeftToRight()) {
  534. levels = BidiUtils.getLevels(bidi);
  535. int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
  536. charsLtoV = BidiUtils.createInverseMap(charsVtoL);
  537. isDirectionLTR = bidi.baseIsLeftToRight();
  538. }
  539. }
  540. Decoration decorator;
  541. if (attributes != null) {
  542. decorator = Decoration.getDecoration(StyledParagraph.addInputMethodAttrs(attributes));
  543. }
  544. else {
  545. decorator = Decoration.getPlainDecoration();
  546. }
  547. int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
  548. TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
  549. TextLineComponent[] components = new TextLineComponent[1];
  550. components = createComponentsOnRun(0, chars.length,
  551. chars,
  552. charsLtoV, levels,
  553. factory, font, lm,
  554. frc,
  555. decorator,
  556. components,
  557. 0);
  558. int numComponents = components.length;
  559. while (components[numComponents-1] == null) {
  560. numComponents -= 1;
  561. }
  562. if (numComponents != components.length) {
  563. TextLineComponent[] temp = new TextLineComponent[numComponents];
  564. System.arraycopy(components, 0, temp, 0, numComponents);
  565. components = temp;
  566. }
  567. return new TextLine(components, lm.baselineOffsets,
  568. chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
  569. }
  570. private static TextLineComponent[] expandArray(TextLineComponent[] orig) {
  571. TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
  572. System.arraycopy(orig, 0, newComponents, 0, orig.length);
  573. return newComponents;
  574. }
  575. /**
  576. * Returns an array in logical order of the TextLineComponents on
  577. * the text in the given range, with the given attributes.
  578. */
  579. public static TextLineComponent[] createComponentsOnRun(int runStart,
  580. int runLimit,
  581. char[] chars,
  582. int[] charsLtoV,
  583. byte[] levels,
  584. TextLabelFactory factory,
  585. Font font,
  586. CoreMetrics cm,
  587. FontRenderContext frc,
  588. Decoration decorator,
  589. TextLineComponent[] components,
  590. int numComponents) {
  591. int pos = runStart;
  592. do {
  593. int chunkLimit = firstVisualChunk(charsLtoV, levels, pos, runLimit); // <= displayLimit
  594. do {
  595. int startPos = pos;
  596. int lmCount;
  597. if (cm == null) {
  598. LineMetrics lineMetrics = font.getLineMetrics(chars, startPos, chunkLimit, frc);
  599. cm = CoreMetrics.get(lineMetrics);
  600. lmCount = lineMetrics.getNumChars();
  601. }
  602. else {
  603. lmCount = (chunkLimit-startPos);
  604. }
  605. TextLineComponent nextComponent =
  606. factory.createExtended(font, cm, decorator, startPos, startPos + lmCount);
  607. ++numComponents;
  608. if (numComponents >= components.length) {
  609. components = expandArray(components);
  610. }
  611. components[numComponents-1] = nextComponent;
  612. pos += lmCount;
  613. } while (pos < chunkLimit);
  614. } while (pos < runLimit);
  615. return components;
  616. }
  617. /**
  618. * Returns an array (in logical order) of the TextLineComponents representing
  619. * the text. The components are both logically and visually contiguous.
  620. */
  621. public static TextLineComponent[] getComponents(StyledParagraph styledParagraph,
  622. char[] chars,
  623. int textStart,
  624. int textLimit,
  625. int[] charsLtoV,
  626. byte[] levels,
  627. TextLabelFactory factory) {
  628. FontRenderContext frc = factory.getFontRenderContext();
  629. int numComponents = 0;
  630. TextLineComponent[] tempComponents = new TextLineComponent[1];
  631. int pos = textStart;
  632. do {
  633. int runLimit = Math.min(styledParagraph.getRunLimit(pos), textLimit);
  634. Decoration decorator = styledParagraph.getDecorationAt(pos);
  635. Object graphicOrFont = styledParagraph.getFontOrGraphicAt(pos);
  636. if (graphicOrFont instanceof GraphicAttribute) {
  637. GraphicAttribute graphicAttribute = (GraphicAttribute) graphicOrFont;
  638. do {
  639. int chunkLimit = firstVisualChunk(charsLtoV, levels,
  640. pos, runLimit);
  641. GraphicComponent nextGraphic =
  642. new GraphicComponent(graphicAttribute, decorator, charsLtoV, levels, pos, chunkLimit);
  643. pos = chunkLimit;
  644. ++numComponents;
  645. if (numComponents >= tempComponents.length) {
  646. tempComponents = expandArray(tempComponents);
  647. }
  648. tempComponents[numComponents-1] = nextGraphic;
  649. } while(pos < runLimit);
  650. }
  651. else {
  652. Font font = (Font) graphicOrFont;
  653. tempComponents = createComponentsOnRun(pos, runLimit,
  654. chars,
  655. charsLtoV, levels,
  656. factory, font, null,
  657. frc,
  658. decorator,
  659. tempComponents,
  660. numComponents);
  661. pos = runLimit;
  662. numComponents = tempComponents.length;
  663. while (tempComponents[numComponents-1] == null) {
  664. numComponents -= 1;
  665. }
  666. }
  667. } while (pos < textLimit);
  668. TextLineComponent[] components;
  669. if (tempComponents.length == numComponents) {
  670. components = tempComponents;
  671. }
  672. else {
  673. components = new TextLineComponent[numComponents];
  674. System.arraycopy(tempComponents, 0, components, 0, numComponents);
  675. }
  676. return components;
  677. }
  678. /**
  679. * Create a TextLine from the Font and character data over the
  680. * range. The range is relative to both the StyledParagraph and the
  681. * character array.
  682. */
  683. public static TextLine createLineFromText(char[] chars,
  684. StyledParagraph styledParagraph,
  685. TextLabelFactory factory,
  686. boolean isDirectionLTR,
  687. float[] baselineOffsets) {
  688. factory.setLineContext(0, chars.length);
  689. Bidi lineBidi = factory.getLineBidi();
  690. int[] charsLtoV = null;
  691. byte[] levels = null;
  692. if (lineBidi != null) {
  693. levels = BidiUtils.getLevels(lineBidi);
  694. int[] charsVtoL = BidiUtils.createVisualToLogicalMap(levels);
  695. charsLtoV = BidiUtils.createInverseMap(charsVtoL);
  696. }
  697. TextLineComponent[] components =
  698. getComponents(styledParagraph, chars, 0, chars.length, charsLtoV, levels, factory);
  699. return new TextLine(components, baselineOffsets,
  700. chars, 0, chars.length, charsLtoV, levels, isDirectionLTR);
  701. }
  702. /**
  703. * Compute the components order from the given components array and
  704. * logical-to-visual character mapping. May return null if canonical.
  705. */
  706. private static int[] computeComponentOrder(TextLineComponent[] components,
  707. int[] charsLtoV) {
  708. /*
  709. * Create a visual ordering for the glyph sets. The important thing
  710. * here is that the values have the proper rank with respect to
  711. * each other, not the exact values. For example, the first glyph
  712. * set that appears visually should have the lowest value. The last
  713. * should have the highest value. The values are then normalized
  714. * to map 1-1 with positions in glyphs.
  715. *
  716. */
  717. int[] componentOrder = null;
  718. if (charsLtoV != null && components.length > 1) {
  719. componentOrder = new int[components.length];
  720. int gStart = 0;
  721. for (int i = 0; i < components.length; i++) {
  722. componentOrder[i] = charsLtoV[gStart];
  723. gStart += components[i].getNumCharacters();
  724. }
  725. componentOrder = BidiUtils.createContiguousOrder(componentOrder);
  726. componentOrder = BidiUtils.createInverseMap(componentOrder);
  727. }
  728. return componentOrder;
  729. }
  730. /**
  731. * Create a TextLine from the text. chars is just the text in the iterator.
  732. */
  733. public static TextLine standardCreateTextLine(FontRenderContext frc,
  734. AttributedCharacterIterator text,
  735. char[] chars,
  736. float[] baselineOffsets) {
  737. StyledParagraph styledParagraph = new StyledParagraph(text, chars);
  738. Bidi bidi = new Bidi(text);
  739. if (bidi.isLeftToRight()) {
  740. bidi = null;
  741. }
  742. int layoutFlags = 0; // no extra info yet, bidi determines run and line direction
  743. TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi, layoutFlags);
  744. boolean isDirectionLTR = true;
  745. if (bidi != null) {
  746. isDirectionLTR = bidi.baseIsLeftToRight();
  747. }
  748. return createLineFromText(chars, styledParagraph, factory, isDirectionLTR, baselineOffsets);
  749. }
  750. /*
  751. * A utility to get a range of text that is both logically and visually
  752. * contiguous.
  753. * If the entire range is ok, return limit, otherwise return the first
  754. * directional change after start. We could do better than this, but
  755. * it doesn't seem worth it at the moment.
  756. private static int firstVisualChunk(int order[], byte direction[],
  757. int start, int limit)
  758. {
  759. if (order != null) {
  760. int min = order[start];
  761. int max = order[start];
  762. int count = limit - start;
  763. for (int i = start + 1; i < limit; i++) {
  764. min = Math.min(min, order[i]);
  765. max = Math.max(max, order[i]);
  766. if (max - min >= count) {
  767. if (direction != null) {
  768. byte baseLevel = direction[start];
  769. for (int j = start + 1; j < i; j++) {
  770. if (direction[j] != baseLevel) {
  771. return j;
  772. }
  773. }
  774. }
  775. return i;
  776. }
  777. }
  778. }
  779. return limit;
  780. }
  781. */
  782. /**
  783. * When this returns, the ACI's current position will be at the start of the
  784. * first run which does NOT contain a GraphicAttribute. If no such run exists
  785. * the ACI's position will be at the end, and this method will return false.
  786. */
  787. static boolean advanceToFirstFont(AttributedCharacterIterator aci) {
  788. for (char ch = aci.first(); ch != aci.DONE; ch = aci.setIndex(aci.getRunLimit())) {
  789. if (aci.getAttribute(TextAttribute.CHAR_REPLACEMENT) == null) {
  790. return true;
  791. }
  792. }
  793. return false;
  794. }
  795. static float[] getNormalizedOffsets(float[] baselineOffsets, byte baseline) {
  796. if (baselineOffsets[baseline] != 0) {
  797. float base = baselineOffsets[baseline];
  798. float[] temp = new float[baselineOffsets.length];
  799. for (int i = 0; i < temp.length; i++)
  800. temp[i] = baselineOffsets[i] - base;
  801. baselineOffsets = temp;
  802. }
  803. return baselineOffsets;
  804. }
  805. static Font getFontAtCurrentPos(AttributedCharacterIterator aci) {
  806. Object value = aci.getAttribute(TextAttribute.FONT);
  807. if (value != null) {
  808. return (Font) value;
  809. }
  810. if (aci.getAttribute(TextAttribute.FAMILY) != null) {
  811. return Font.getFont(aci.getAttributes());
  812. }
  813. int ch = CodePointIterator.create(aci).next();
  814. if (ch != CodePointIterator.DONE) {
  815. FontResolver resolver = FontResolver.getInstance();
  816. return resolver.getFont(resolver.getFontIndex(ch), aci.getAttributes());
  817. }
  818. return null;
  819. }
  820. /**
  821. * Utility method for getting justification ratio from attributes.
  822. */
  823. static float getJustifyRatio(Map attributes) {
  824. Object value = attributes.get(TextAttribute.JUSTIFICATION);
  825. if (value == null) {
  826. return 1;
  827. }
  828. float justifyRatio = ((Float)value).floatValue();
  829. if (justifyRatio < 0) {
  830. justifyRatio = 0;
  831. }
  832. else if (justifyRatio > 1) {
  833. justifyRatio = 1;
  834. }
  835. return justifyRatio;
  836. }
  837. /*
  838. * The new version requires that chunks be at the same level.
  839. */
  840. private static int firstVisualChunk(int order[], byte direction[],
  841. int start, int limit)
  842. {
  843. if (order != null && direction != null) {
  844. byte dir = direction[start];
  845. while (++start < limit && direction[start] == dir) {}
  846. return start;
  847. }
  848. return limit;
  849. }
  850. /*
  851. * create a new line with characters between charStart and charLimit
  852. * justified using the provided width and ratio.
  853. */
  854. public TextLine getJustifiedLine(float justificationWidth, float justifyRatio, int justStart, int justLimit) {
  855. TextLineComponent[] newComponents = new TextLineComponent[fComponents.length];
  856. System.arraycopy(fComponents, 0, newComponents, 0, fComponents.length);
  857. float leftHang = 0;
  858. float adv = 0;
  859. float justifyDelta = 0;
  860. boolean rejustify = false;
  861. do {
  862. adv = getAdvanceBetween(newComponents, 0, characterCount());
  863. // all characters outside the justification range must be in the base direction
  864. // of the layout, otherwise justification makes no sense.
  865. float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit);
  866. // get the actual justification delta
  867. justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio;
  868. // generate an array of GlyphJustificationInfo records to pass to
  869. // the justifier. Array is visually ordered.
  870. // get positions that each component will be using
  871. int[] infoPositions = new int[newComponents.length];
  872. int infoCount = 0;
  873. for (int visIndex = 0; visIndex < newComponents.length; visIndex++) {
  874. int logIndex = fComponentVisualOrder == null ? visIndex : fComponentVisualOrder[visIndex];
  875. infoPositions[logIndex] = infoCount;
  876. infoCount += newComponents[logIndex].getNumJustificationInfos();
  877. }
  878. GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount];
  879. // get justification infos
  880. int compStart = 0;
  881. for (int i = 0; i < newComponents.length; i++) {
  882. TextLineComponent comp = newComponents[i];
  883. int compLength = comp.getNumCharacters();
  884. int compLimit = compStart + compLength;
  885. if (compLimit > justStart) {
  886. int rangeMin = Math.max(0, justStart - compStart);
  887. int rangeMax = Math.min(compLength, justLimit - compStart);
  888. comp.getJustificationInfos(infos, infoPositions[i], rangeMin, rangeMax);
  889. if (compLimit >= justLimit) {
  890. break;
  891. }
  892. }
  893. }
  894. // records are visually ordered, and contiguous, so start and end are
  895. // simply the places where we didn't fetch records
  896. int infoStart = 0;
  897. int infoLimit = infoCount;
  898. while (infoStart < infoLimit && infos[infoStart] == null) {
  899. ++infoStart;
  900. }
  901. while (infoLimit > infoStart && infos[infoLimit - 1] == null) {
  902. --infoLimit;
  903. }
  904. // invoke justifier on the records
  905. TextJustifier justifier = new TextJustifier(infos, infoStart, infoLimit);
  906. float[] deltas = justifier.justify(justifyDelta);
  907. boolean canRejustify = rejustify == false;
  908. boolean wantRejustify = false;
  909. boolean[] flags = new boolean[1];
  910. // apply justification deltas
  911. compStart = 0;
  912. for (int i = 0; i < newComponents.length; i++) {
  913. TextLineComponent comp = newComponents[i];
  914. int compLength = comp.getNumCharacters();
  915. int compLimit = compStart + compLength;
  916. if (compLimit > justStart) {
  917. int rangeMin = Math.max(0, justStart - compStart);
  918. int rangeMax = Math.min(compLength, justLimit - compStart);
  919. newComponents[i] = comp.applyJustificationDeltas(deltas, infoPositions[i] * 2, flags);
  920. wantRejustify |= flags[0];
  921. if (compLimit >= justLimit) {
  922. break;
  923. }
  924. }
  925. }
  926. rejustify = wantRejustify && !rejustify; // only make two passes
  927. } while (rejustify);
  928. return new TextLine(newComponents, fBaselineOffsets, fChars, fCharsStart,
  929. fCharsLimit, fCharLogicalOrder, fCharLevels,
  930. fIsDirectionLTR);
  931. }
  932. // return the sum of the advances of text between the logical start and limit
  933. public static float getAdvanceBetween(TextLineComponent[] components, int start, int limit) {
  934. float advance = 0;
  935. int tlcStart = 0;
  936. for(int i = 0; i < components.length; i++) {
  937. TextLineComponent comp = components[i];
  938. int tlcLength = comp.getNumCharacters();
  939. int tlcLimit = tlcStart + tlcLength;
  940. if (tlcLimit > start) {
  941. int measureStart = Math.max(0, start - tlcStart);
  942. int measureLimit = Math.min(tlcLength, limit - tlcStart);
  943. advance += comp.getAdvanceBetween(measureStart, measureLimit);
  944. if (tlcLimit >= limit) {
  945. break;
  946. }
  947. }
  948. tlcStart = tlcLimit;
  949. }
  950. return advance;
  951. }
  952. }