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