1. /*
  2. * %W% %E%
  3. *
  4. * Copyright 1998-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. /*
  11. * (C) Copyright IBM Corp. 1998, All Rights Reserved
  12. *
  13. */
  14. package java.awt.font;
  15. import java.awt.Font;
  16. import java.awt.Graphics2D;
  17. import java.awt.Shape;
  18. import java.awt.Toolkit;
  19. import java.awt.geom.Rectangle2D;
  20. import java.awt.geom.AffineTransform;
  21. import java.awt.geom.GeneralPath;
  22. import java.awt.im.InputMethodHighlight;
  23. import java.text.CharacterIterator;
  24. import java.text.AttributedCharacterIterator;
  25. import java.text.Annotation;
  26. import java.util.Map;
  27. import java.util.Hashtable;
  28. import sun.awt.font.Bidi;
  29. import sun.awt.font.ExtendedTextLabel;
  30. import sun.awt.font.ExtendedTextLabelComponent;
  31. import sun.awt.font.GraphicComponent;
  32. import sun.awt.font.TextLabelFactory;
  33. import sun.awt.font.TextLineComponent;
  34. import sun.java2d.SunGraphicsEnvironment;
  35. // TO DO: get rid of FontSource - use something else or go back to ACI
  36. final class TextLine {
  37. static final class TextLineMetrics {
  38. public final float ascent;
  39. public final float descent;
  40. public final float leading;
  41. public final float advance;
  42. public TextLineMetrics(float ascent,
  43. float descent,
  44. float leading,
  45. float advance) {
  46. this.ascent = ascent;
  47. this.descent = descent;
  48. this.leading = leading;
  49. this.advance = advance;
  50. }
  51. }
  52. private TextLineComponent[] fComponents;
  53. private float[] fBaselineOffsets;
  54. private int[] fComponentVisualOrder; // if null, ltr
  55. private char[] fChars;
  56. private int fCharsStart;
  57. private int fCharsLimit;
  58. private int[] fCharVisualOrder; // if null, ltr
  59. private int[] fCharLogicalOrder; // if null, ltr
  60. private byte[] fCharLevels; // if null, 0
  61. private boolean fIsDirectionLTR;
  62. private TextLineMetrics fMetrics = null; // built on demand in getMetrics
  63. public TextLine(TextLineComponent[] components,
  64. float[] baselineOffsets,
  65. char[] chars,
  66. int charsStart,
  67. int charsLimit,
  68. int[] charLogicalOrder,
  69. byte[] charLevels,
  70. boolean isDirectionLTR) {
  71. int[] componentVisualOrder = computeComponentOrder(components,
  72. charLogicalOrder);
  73. fComponents = components;
  74. fBaselineOffsets = baselineOffsets;
  75. fComponentVisualOrder = componentVisualOrder;
  76. fChars = chars;
  77. fCharsStart = charsStart;
  78. fCharsLimit = charsLimit;
  79. fCharLogicalOrder = charLogicalOrder;
  80. fCharLevels = charLevels;
  81. fIsDirectionLTR = isDirectionLTR;
  82. checkCtorArgs();
  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 abstract static class Function {
  96. abstract float computeFunction(TextLine line,
  97. int componentIndex,
  98. int indexInArray);
  99. }
  100. private static Function fgAdvanceF = new Function() {
  101. float computeFunction(TextLine line,
  102. int componentIndex,
  103. int indexInArray) {
  104. TextLineComponent tlc = line.fComponents[componentIndex];
  105. return tlc.getCharAdvance(indexInArray);
  106. }
  107. };
  108. private static Function fgXPositionF = new Function() {
  109. float computeFunction(TextLine line,
  110. int componentIndex,
  111. int indexInArray) {
  112. // add up Component advances before componentIndex
  113. // Note how we have to get the component advances. It would
  114. // be better to have a getAdvance mathod on component.
  115. float componentsAdvance = 0;
  116. if (line.fComponentVisualOrder == null) {
  117. for (int i=0; i < componentIndex; i++) {
  118. Rectangle2D lb = line.fComponents[i].getLogicalBounds();
  119. componentsAdvance += (float)lb.getWidth();
  120. }
  121. }
  122. else {
  123. for (int i=0; line.fComponentVisualOrder[i] != componentIndex; i++) {
  124. int index = line.fComponentVisualOrder[i];
  125. Rectangle2D lb = line.fComponents[index].getLogicalBounds();
  126. componentsAdvance += (float)lb.getWidth();
  127. }
  128. }
  129. TextLineComponent tlc = line.fComponents[componentIndex];
  130. return componentsAdvance + tlc.getCharX(indexInArray);
  131. }
  132. };
  133. private static Function fgYPositionF = new Function() {
  134. float computeFunction(TextLine line,
  135. int componentIndex,
  136. int indexInArray) {
  137. TextLineComponent tlc = line.fComponents[componentIndex];
  138. float charPos = tlc.getCharY(indexInArray);
  139. // charPos is relative to the component - adjust for
  140. // baseline
  141. return charPos + line.getComponentShift(componentIndex);
  142. }
  143. };
  144. public int characterCount() {
  145. return fCharsLimit - fCharsStart;
  146. }
  147. public boolean isDirectionLTR() {
  148. return fIsDirectionLTR;
  149. }
  150. public TextLineMetrics getMetrics() {
  151. if (fMetrics == null) {
  152. float ascent = 0;
  153. float descent = 0;
  154. float leading = 0;
  155. float advance = 0;
  156. // ascent + descent must not be less than this value
  157. float maxGraphicHeight = 0;
  158. float maxGraphicHeightWithLeading = 0;
  159. // walk through EGA's
  160. TextLineComponent tlc;
  161. boolean fitTopAndBottomGraphics = false;
  162. for (int i = 0; i < fComponents.length; i++) {
  163. tlc = fComponents[i];
  164. Rectangle2D lb = tlc.getLogicalBounds();
  165. advance += (float)lb.getWidth();
  166. byte baseline = (byte) tlc.getLineMetrics().getBaselineIndex();
  167. LineMetrics lm = tlc.getLineMetrics();
  168. if (baseline >= 0) {
  169. float baselineOffset = fBaselineOffsets[baseline];
  170. ascent = Math.max(ascent, -baselineOffset + lm.getAscent());
  171. float gd = baselineOffset + lm.getDescent();
  172. descent = Math.max(descent, gd);
  173. leading = Math.max(leading, gd + lm.getLeading());
  174. }
  175. else {
  176. fitTopAndBottomGraphics = true;
  177. float graphicHeight = lm.getAscent() + lm.getDescent();
  178. float graphicHeightWithLeading = graphicHeight + lm.getLeading();
  179. maxGraphicHeight = Math.max(maxGraphicHeight,
  180. graphicHeight);
  181. maxGraphicHeightWithLeading = Math.max(maxGraphicHeightWithLeading,
  182. graphicHeightWithLeading);
  183. }
  184. }
  185. if (fitTopAndBottomGraphics) {
  186. if (maxGraphicHeight > ascent + descent) {
  187. descent = maxGraphicHeight - ascent;
  188. }
  189. if (maxGraphicHeightWithLeading > ascent + leading) {
  190. leading = maxGraphicHeightWithLeading - ascent;
  191. }
  192. }
  193. leading -= descent;
  194. fMetrics = new TextLineMetrics(ascent, descent, leading, advance);
  195. }
  196. return fMetrics;
  197. }
  198. public int visualToLogical(int visualIndex) {
  199. if (fCharLogicalOrder == null) {
  200. return visualIndex;
  201. }
  202. if (fCharVisualOrder == null) {
  203. fCharVisualOrder = Bidi.getInverseOrder(fCharLogicalOrder);
  204. }
  205. return fCharVisualOrder[visualIndex];
  206. }
  207. public int logicalToVisual(int logicalIndex) {
  208. return (fCharLogicalOrder == null)?
  209. logicalIndex : fCharLogicalOrder[logicalIndex];
  210. }
  211. public byte getCharLevel(int logicalIndex) {
  212. return fCharLevels==null? 0 : fCharLevels[logicalIndex];
  213. }
  214. public boolean isCharLTR(int logicalIndex) {
  215. return (getCharLevel(logicalIndex) & 0x1) == 0;
  216. }
  217. public int getCharType(int logicalIndex) {
  218. return Character.getType(fChars[logicalIndex + fCharsStart]);
  219. }
  220. public boolean isCharSpace(int logicalIndex) {
  221. return Character.isSpaceChar(fChars[logicalIndex + fCharsStart]);
  222. }
  223. public boolean isCharWhitespace(int logicalIndex) {
  224. return Character.isWhitespace(fChars[logicalIndex + fCharsStart]);
  225. }
  226. public float getCharAngle(int logicalIndex) {
  227. if (logicalIndex < 0) {
  228. throw new IllegalArgumentException("Negative logicalIndex.");
  229. }
  230. if (logicalIndex > fCharsLimit - fCharsStart) {
  231. throw new IllegalArgumentException("logicalIndex too large.");
  232. }
  233. int currentTlc = 0;
  234. int tlcLimit = 0;
  235. do {
  236. tlcLimit += fComponents[currentTlc].getNumCharacters();
  237. if (tlcLimit > logicalIndex) {
  238. break;
  239. }
  240. ++currentTlc;
  241. } while(currentTlc < fComponents.length);
  242. return fComponents[currentTlc].getItalicAngle();
  243. }
  244. private LineMetrics getLineMetricsAt(int logicalIndex) {
  245. if (logicalIndex < 0) {
  246. throw new IllegalArgumentException("Negative logicalIndex.");
  247. }
  248. if (logicalIndex > fCharsLimit - fCharsStart) {
  249. throw new IllegalArgumentException("logicalIndex too large.");
  250. }
  251. int currentTlc = 0;
  252. int tlcStart = 0;
  253. int tlcLimit = 0;
  254. do {
  255. tlcLimit += fComponents[currentTlc].getNumCharacters();
  256. if (tlcLimit > logicalIndex) {
  257. break;
  258. }
  259. ++currentTlc;
  260. tlcStart = tlcLimit;
  261. } while(currentTlc < fComponents.length);
  262. return fComponents[currentTlc].getLineMetrics();
  263. }
  264. public float getCharAscent(int logicalIndex) {
  265. return getLineMetricsAt(logicalIndex).getAscent();
  266. }
  267. public float getCharDescent(int logicalIndex) {
  268. return getLineMetricsAt(logicalIndex).getDescent();
  269. }
  270. private float applyFunctionAtIndex(int logicalIndex, Function f) {
  271. if (logicalIndex < 0) {
  272. throw new IllegalArgumentException("Negative logicalIndex.");
  273. }
  274. int tlcStart = 0;
  275. for(int i=0; i < fComponents.length; i++) {
  276. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  277. if (tlcLimit > logicalIndex) {
  278. return f.computeFunction(this, i, logicalIndex - tlcStart);
  279. }
  280. else {
  281. tlcStart = tlcLimit;
  282. }
  283. }
  284. throw new IllegalArgumentException("logicalIndex too large.");
  285. }
  286. public float getCharAdvance(int logicalIndex) {
  287. return applyFunctionAtIndex(logicalIndex, fgAdvanceF);
  288. }
  289. public float getCharXPosition(int logicalIndex) {
  290. return applyFunctionAtIndex(logicalIndex, fgXPositionF);
  291. }
  292. public float getCharYPosition(int logicalIndex) {
  293. return applyFunctionAtIndex(logicalIndex, fgYPositionF);
  294. }
  295. public float getCharLinePosition(int logicalIndex) {
  296. return getCharXPosition(logicalIndex);
  297. }
  298. public boolean caretAtOffsetIsValid(int offset) {
  299. if (offset < 0) {
  300. throw new IllegalArgumentException("Negative offset.");
  301. }
  302. int tlcStart = 0;
  303. for(int i=0; i < fComponents.length; i++) {
  304. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  305. if (tlcLimit > offset) {
  306. return fComponents[i].caretAtOffsetIsValid(offset-tlcStart);
  307. }
  308. else {
  309. tlcStart = tlcLimit;
  310. }
  311. }
  312. throw new IllegalArgumentException("logicalIndex too large.");
  313. }
  314. public Rectangle2D getCharBounds(int logicalIndex) {
  315. if (logicalIndex < 0) {
  316. throw new IllegalArgumentException("Negative logicalIndex.");
  317. }
  318. int tlcStart = 0;
  319. for (int i=0; i < fComponents.length; i++) {
  320. int tlcLimit = tlcStart + fComponents[i].getNumCharacters();
  321. if (tlcLimit > logicalIndex) {
  322. TextLineComponent tlc = fComponents[i];
  323. int indexInTlc = logicalIndex - tlcStart;
  324. Rectangle2D chBounds = tlc.getCharVisualBounds(indexInTlc);
  325. // now accumulate advances of visually preceding tlc's
  326. float componentsAdvance = 0;
  327. if (fComponentVisualOrder == null) {
  328. for (int j=0; j < i; j++) {
  329. Rectangle2D lb = fComponents[j].getLogicalBounds();
  330. componentsAdvance += (float)lb.getWidth();
  331. }
  332. }
  333. else {
  334. for (int j=0; fComponentVisualOrder[j] != i; j++) {
  335. int index = fComponentVisualOrder[j];
  336. Rectangle2D lb = fComponents[index].getLogicalBounds();
  337. componentsAdvance += (float)lb.getWidth();
  338. }
  339. }
  340. chBounds.setRect(chBounds.getX() + componentsAdvance,
  341. chBounds.getY(),
  342. chBounds.getWidth(),
  343. chBounds.getHeight());
  344. return chBounds;
  345. }
  346. else {
  347. tlcStart = tlcLimit;
  348. }
  349. }
  350. throw new IllegalArgumentException("logicalIndex too large.");
  351. }
  352. private float getComponentShift(int index) {
  353. byte baseline = (byte) fComponents[index].getLineMetrics().getBaselineIndex();
  354. if (baseline >= 0) {
  355. return fBaselineOffsets[baseline];
  356. }
  357. else {
  358. TextLineMetrics lineMetrics = getMetrics();
  359. // don't bother to get chars right here:
  360. LineMetrics compMetrics = fComponents[index].getLineMetrics();
  361. if (baseline == GraphicAttribute.TOP_ALIGNMENT) {
  362. return compMetrics.getAscent() - lineMetrics.ascent;
  363. }
  364. else {
  365. return lineMetrics.descent - compMetrics.getDescent();
  366. }
  367. }
  368. }
  369. public void draw(Graphics2D g2, float x, float y) {
  370. float nx = x;
  371. for (int i = 0; i < fComponents.length; i++) {
  372. int index = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  373. TextLineComponent tlc = fComponents[index];
  374. float shift = getComponentShift(index);
  375. tlc.draw(g2, nx, y + shift);
  376. if (i != fComponents.length-1) {
  377. Rectangle2D lb = tlc.getLogicalBounds();
  378. nx += (float)lb.getWidth();
  379. }
  380. }
  381. }
  382. public Rectangle2D getBounds() {
  383. float tlcAdvance = 0;
  384. float left = Float.MAX_VALUE, right = Float.MIN_VALUE;
  385. float top = Float.MAX_VALUE, bottom = Float.MIN_VALUE;
  386. for (int i=0; i < fComponents.length; i++) {
  387. int index = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  388. TextLineComponent tlc = fComponents[index];
  389. Rectangle2D tlcBounds = tlc.getVisualBounds();
  390. left = Math.min(left, (float) tlcBounds.getX() + tlcAdvance);
  391. right = Math.max(right, (float) tlcBounds.getMaxX() + tlcAdvance);
  392. float shift = getComponentShift(index);
  393. top = Math.min(top, (float) tlcBounds.getY()+shift);
  394. bottom = Math.max(bottom, (float) tlcBounds.getMaxY()+shift);
  395. Rectangle2D lb = tlc.getLogicalBounds();
  396. tlcAdvance += (float)lb.getWidth();
  397. }
  398. return new Rectangle2D.Float(left, top, right-left, bottom-top);
  399. }
  400. public Shape getOutline(AffineTransform tx) {
  401. GeneralPath dstShape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
  402. float x = 0;
  403. for (int i=0; i < fComponents.length; i++) {
  404. int index = fComponentVisualOrder==null? i : fComponentVisualOrder[i];
  405. TextLineComponent tlc = fComponents[index];
  406. float shift = getComponentShift(index);
  407. dstShape.append(tlc.getOutline(x, shift), false);
  408. Rectangle2D lb = tlc.getLogicalBounds();
  409. x += (float)lb.getWidth();
  410. }
  411. if (tx != null) {
  412. dstShape.transform(tx);
  413. }
  414. return dstShape;
  415. }
  416. public int hashCode() {
  417. return (fComponents.length << 16) ^
  418. (fComponents[0].hashCode() << 3) ^ (fCharsLimit-fCharsStart);
  419. }
  420. public String toString() {
  421. StringBuffer buf = new StringBuffer();
  422. for (int i = 0; i < fComponents.length; i++) {
  423. buf.append(fComponents[i]);
  424. }
  425. return buf.toString();
  426. }
  427. /**
  428. * Create a TextLine from the text. The Font must be able to
  429. * display all of the text.
  430. * attributes==null is equivalent to using an empty Map for
  431. * attributes
  432. */
  433. public static TextLine fastCreateTextLine(FontRenderContext frc,
  434. char[] text,
  435. int start,
  436. int limit,
  437. Font font,
  438. LineMetrics lm,
  439. Map attributes) {
  440. boolean isDirectionLTR = true;
  441. byte[] levels = null;
  442. int[] charsLtoV = null;
  443. Bidi bidi = null;
  444. char[] chars;
  445. int characterCount = limit - start;
  446. if (start != 0) {
  447. chars = new char[characterCount];
  448. System.arraycopy(text, start, chars, 0, characterCount);
  449. }
  450. else {
  451. chars = text;
  452. }
  453. boolean requiresBidi = false;
  454. boolean directionKnown = false;
  455. byte[] embs = null;
  456. if (attributes != null) {
  457. try {
  458. Boolean runDirection = (Boolean)attributes.get(TextAttribute.RUN_DIRECTION);
  459. if (runDirection != null) {
  460. directionKnown = true;
  461. isDirectionLTR = TextAttribute.RUN_DIRECTION_LTR.equals(runDirection);
  462. requiresBidi = !isDirectionLTR;
  463. }
  464. }
  465. catch (ClassCastException e) {
  466. }
  467. try {
  468. Integer embeddingLevel = (Integer)attributes.get(TextAttribute.BIDI_EMBEDDING);
  469. if (embeddingLevel != null) {
  470. int intLevel = embeddingLevel.intValue();
  471. if (intLevel >= -15 && intLevel < 16) {
  472. byte level = (byte)intLevel;
  473. requiresBidi = true;
  474. embs = new byte[characterCount];
  475. for (int i = 0; i < embs.length; ++i) {
  476. embs[i] = level;
  477. }
  478. }
  479. }
  480. }
  481. catch (ClassCastException e) {
  482. }
  483. }
  484. if (!requiresBidi) {
  485. for (int i = 0; i < chars.length; i++) {
  486. if (Bidi.requiresBidi(chars[i])) {
  487. requiresBidi = true;
  488. break;
  489. }
  490. }
  491. }
  492. if (requiresBidi) {
  493. if (!directionKnown) {
  494. isDirectionLTR = Bidi.defaultIsLTR(chars, 0, characterCount);
  495. }
  496. if (embs == null) {
  497. embs = Bidi.getEmbeddingArray(chars, isDirectionLTR);
  498. }
  499. bidi = new Bidi(chars, embs, isDirectionLTR);
  500. levels = bidi.getLevels();
  501. charsLtoV = bidi.getLogicalToVisualMap();
  502. }
  503. if (attributes != null) {
  504. attributes = addInputMethodAttrs(attributes);
  505. }
  506. TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi);
  507. TextLineComponent[] components;
  508. // one component per level
  509. if (bidi == null || bidi.getLevelLimit(0) == characterCount) {
  510. components = new TextLineComponent[1];
  511. ExtendedTextLabel label =
  512. factory.createExtended(font, lm, 0, characterCount);
  513. components[0] = new ExtendedTextLabelComponent(label, attributes);
  514. } else {
  515. int count = 0;
  516. int pos = 0;
  517. while (pos < characterCount) {
  518. pos = bidi.getLevelLimit(pos);
  519. ++count;
  520. }
  521. components = new TextLineComponent[count];
  522. count = 0;
  523. pos = 0;
  524. while (pos < characterCount) {
  525. int newpos = bidi.getLevelLimit(pos);
  526. ExtendedTextLabel label = factory.createExtended(font, lm, pos, newpos);
  527. components[count] = new ExtendedTextLabelComponent(label, attributes);
  528. ++count;
  529. pos = newpos;
  530. }
  531. }
  532. return new TextLine(components, lm.getBaselineOffsets(),
  533. text, start, limit, charsLtoV, levels, isDirectionLTR);
  534. }
  535. static abstract class FontSource {
  536. // 0-based
  537. abstract int getLength();
  538. abstract int getRunLimit(int pos);
  539. /**
  540. * Return graphic at position. Null if no graphic.
  541. */
  542. abstract GraphicAttribute graphicAt(int pos);
  543. /**
  544. * Return font at position. Does not try to substitute
  545. * another font. Null if no font explicitly on the text.
  546. */
  547. abstract Font fontAt(int pos);
  548. /**
  549. * Compute best font for text. Should use
  550. * SunGraphicsEnvironment.getBestFontFor.
  551. */
  552. abstract Font getBestFontAt(int pos);
  553. /**
  554. * Get attributes.
  555. */
  556. abstract Map attributesAt(int pos);
  557. }
  558. static class ACIFontSource extends FontSource {
  559. private AttributedCharacterIterator fIter;
  560. private int fIterStart;
  561. public ACIFontSource(AttributedCharacterIterator iter) {
  562. fIter = iter;
  563. fIterStart = iter.getBeginIndex();
  564. }
  565. int getLength() {
  566. return fIter.getEndIndex() - fIterStart;
  567. }
  568. int getRunLimit(int pos) {
  569. fIter.setIndex(pos + fIterStart);
  570. return fIter.getRunLimit() - fIterStart;
  571. }
  572. GraphicAttribute graphicAt(int pos) {
  573. fIter.setIndex(pos + fIterStart);
  574. return (GraphicAttribute)
  575. fIter.getAttribute(TextAttribute.CHAR_REPLACEMENT);
  576. }
  577. Font fontAt(int pos) {
  578. fIter.setIndex(pos + fIterStart);
  579. return (Font) fIter.getAttribute(TextAttribute.FONT);
  580. }
  581. Font getBestFontAt(int pos) {
  582. int iterPos = pos + fIterStart;
  583. fIter.setIndex(iterPos);
  584. return SunGraphicsEnvironment.getBestFontFor(
  585. fIter, iterPos, fIter.getRunLimit());
  586. }
  587. Map attributesAt(int pos) {
  588. fIter.setIndex(pos + fIterStart);
  589. return fIter.getAttributes();
  590. }
  591. }
  592. // for getComponents() use - pretty specialized
  593. private static TextLineComponent[] expandArrays(TextLineComponent[] orig) {
  594. TextLineComponent[] newComponents = new TextLineComponent[orig.length + 8];
  595. System.arraycopy(orig, 0, newComponents, 0, orig.length);
  596. return newComponents;
  597. }
  598. private static Map addInputMethodAttrs(Map oldStyles) {
  599. Object value = oldStyles.get(TextAttribute.INPUT_METHOD_HIGHLIGHT);
  600. try {
  601. if (value != null) {
  602. if (value instanceof Annotation) {
  603. value = ((Annotation)value).getValue();
  604. }
  605. InputMethodHighlight hl;
  606. hl = (InputMethodHighlight) value;
  607. Map imStyles = null;
  608. try {
  609. imStyles = hl.getStyle();
  610. } catch (NoSuchMethodError e) {
  611. }
  612. if (imStyles == null) {
  613. Toolkit tk = Toolkit.getDefaultToolkit();
  614. imStyles = tk.mapInputMethodHighlight(hl);
  615. }
  616. if (imStyles != null) {
  617. Hashtable newStyles = new Hashtable(5, (float)0.9);
  618. newStyles.putAll(oldStyles);
  619. newStyles.putAll(imStyles);
  620. return newStyles;
  621. }
  622. }
  623. }
  624. catch(ClassCastException e) {
  625. }
  626. return oldStyles;
  627. }
  628. /**
  629. * Returns an array (in logical order) of the glyphsets representing
  630. * the text. The glyphsets are both logically and visually contiguous.
  631. * If varBaselines is not null, then the array of baselines for the
  632. * TextLineComponents is returned in varBaselines[0].
  633. */
  634. public static TextLineComponent[] getComponents(FontSource fontSource,
  635. char[] chars,
  636. int textStart,
  637. int textLimit,
  638. int[] charsLtoV,
  639. byte[] levels,
  640. TextLabelFactory factory) {
  641. FontRenderContext frc = factory.getFontRenderContext();
  642. int numComponents = 0;
  643. TextLineComponent[] tempComponents = new TextLineComponent[8];
  644. /*
  645. * text may be inside some larger text, be sure to adjust before
  646. * accessing arrays, which map zero to the start of the text.
  647. *
  648. */
  649. int pos = textStart;
  650. do {
  651. //text.setIndex(pos);
  652. int runLimit = fontSource.getRunLimit(pos); // <= textLimit
  653. if (runLimit > textLimit) {
  654. runLimit = textLimit;
  655. }
  656. GraphicAttribute graphicAttribute = fontSource.graphicAt(pos);
  657. if (graphicAttribute != null) {
  658. do {
  659. int chunkLimit =
  660. textStart + firstVisualChunk(charsLtoV, levels,
  661. pos - textStart, runLimit - textStart);
  662. Map attrs = fontSource.attributesAt(pos);
  663. GraphicComponent nextGraphic =
  664. new GraphicComponent(graphicAttribute, attrs, charsLtoV, levels, pos-textStart, chunkLimit-textStart);
  665. pos = chunkLimit;
  666. ++numComponents;
  667. if (numComponents >= tempComponents.length) {
  668. tempComponents = expandArrays(tempComponents);
  669. }
  670. tempComponents[numComponents-1] = nextGraphic;
  671. } while(pos < runLimit);
  672. }
  673. else {
  674. do {
  675. /*
  676. * If the client has indicated a font, they're responsible for
  677. * ensuring that it can display all the text to which it is
  678. * applied. We won't do anything to handle it.
  679. */
  680. int displayLimit = runLimit; // default
  681. Font font = fontSource.fontAt(pos);
  682. if (font == null) {
  683. font = fontSource.getBestFontAt(pos);
  684. // !!! REMIND dlf 081498
  685. // this can cause the same char with the same style to use different fonts
  686. // if the limit is less than the entire run.
  687. displayLimit = font.canDisplayUpTo(chars, pos, runLimit);
  688. if (displayLimit == pos) {
  689. ++displayLimit;
  690. }
  691. }
  692. do {
  693. int chunkLimit = textStart + firstVisualChunk(charsLtoV,
  694. levels, pos - textStart,
  695. displayLimit - textStart); // <= displayLimit
  696. do {
  697. Map attrs = fontSource.attributesAt(pos);
  698. int startPos = pos;
  699. LineMetrics lm = font.getLineMetrics(chars, pos, chunkLimit, frc);
  700. pos += lm.getNumChars();
  701. ExtendedTextLabel ga =
  702. factory.createExtended(font, lm, startPos, pos);
  703. attrs = addInputMethodAttrs(attrs);
  704. TextLineComponent nextComponent =
  705. new ExtendedTextLabelComponent(ga, attrs);
  706. ++numComponents;
  707. if (numComponents >= tempComponents.length) {
  708. tempComponents = expandArrays(tempComponents);
  709. }
  710. tempComponents[numComponents-1] = nextComponent;
  711. } while (pos < chunkLimit);
  712. } while (pos < displayLimit);
  713. } while (pos < runLimit);
  714. }
  715. } while (pos < textLimit);
  716. TextLineComponent[] components;
  717. if (tempComponents.length == numComponents) {
  718. components = tempComponents;
  719. }
  720. else {
  721. components = new TextLineComponent[numComponents];
  722. System.arraycopy(tempComponents, 0, components, 0, numComponents);
  723. }
  724. return components;
  725. }
  726. /**
  727. * Create a TextLine from the Font and character data over the
  728. * range. The range is relative to both the FontSource and the
  729. * character array.
  730. */
  731. public static TextLine createLineFromText(char[] chars,
  732. int start,
  733. int limit,
  734. FontSource fontSource,
  735. TextLabelFactory factory,
  736. boolean isDirectionLTR,
  737. float[] baselineOffsets) {
  738. factory.setLineContext(start, limit);
  739. Bidi lineBidi = factory.getLineBidi();
  740. int[] charsLtoV = null;
  741. byte[] levels = null;
  742. if (lineBidi != null) {
  743. charsLtoV = lineBidi.getLogicalToVisualMap();
  744. levels = lineBidi.getLevels();
  745. }
  746. TextLineComponent[] components =
  747. getComponents(fontSource, chars, start, limit, charsLtoV, levels, factory);
  748. return new TextLine(components, baselineOffsets,
  749. chars, start, limit, charsLtoV, levels, isDirectionLTR);
  750. }
  751. /**
  752. * Compute the components order from the given components array and
  753. * logical-to-visual character mapping. May return null if canonical.
  754. */
  755. private static int[] computeComponentOrder(TextLineComponent[] components,
  756. int[] charsLtoV) {
  757. /*
  758. * Create a visual ordering for the glyph sets. The important thing
  759. * here is that the values have the proper rank with respect to
  760. * each other, not the exact values. For example, the first glyph
  761. * set that appears visually should have the lowest value. The last
  762. * should have the highest value. The values are then normalized
  763. * to map 1-1 with positions in glyphs.
  764. *
  765. */
  766. int[] componentOrder = null;
  767. if (charsLtoV != null && components.length > 1) {
  768. componentOrder = new int[components.length];
  769. int gStart = 0;
  770. for (int i = 0; i < components.length; i++) {
  771. componentOrder[i] = charsLtoV[gStart];
  772. gStart += components[i].getNumCharacters();
  773. }
  774. componentOrder = Bidi.getContiguousOrder(componentOrder);
  775. componentOrder = Bidi.getInverseOrder(componentOrder);
  776. }
  777. return componentOrder;
  778. }
  779. /**
  780. * Create a Bidi for the paragraph in <tt>text</tt>. If <tt>chars</tt>
  781. * is not null it must be the characters in the paragraph.
  782. */
  783. // should this live in Bidi??
  784. static Bidi createBidiOnParagraph(AttributedCharacterIterator text,
  785. char[] chars) {
  786. final int begin = text.getBeginIndex();
  787. final int end = text.getEndIndex();
  788. final int length = end - begin;
  789. if (chars == null) {
  790. int n = 0;
  791. chars = new char[length];
  792. for (char c = text.first(); c != text.DONE; c = text.next()) {
  793. chars[n++] = c;
  794. }
  795. }
  796. else {
  797. if (chars.length != length) {
  798. throw new IllegalArgumentException("chars length is not iter length");
  799. }
  800. }
  801. boolean isDirectionLTR = true;
  802. Bidi bidi = null;
  803. boolean requiresBidi = false;
  804. boolean directionKnown = false;
  805. byte[] embs = null;
  806. text.first();
  807. try {
  808. Boolean runDirection = (Boolean)text.getAttribute(TextAttribute.RUN_DIRECTION);
  809. if (runDirection != null) {
  810. directionKnown = true;
  811. isDirectionLTR = TextAttribute.RUN_DIRECTION_LTR.equals(runDirection);
  812. requiresBidi = !isDirectionLTR;
  813. }
  814. }
  815. catch (ClassCastException e) {
  816. }
  817. int pos = begin;
  818. byte level = 0;
  819. byte baselevel = (byte)((directionKnown && !isDirectionLTR) ? 1 : 0);
  820. do {
  821. text.setIndex(pos);
  822. Object embeddingLevel = text.getAttribute(TextAttribute.BIDI_EMBEDDING);
  823. int newpos = text.getRunLimit(TextAttribute.BIDI_EMBEDDING);
  824. if (embeddingLevel != null) {
  825. try {
  826. int intLevel = ((Integer)embeddingLevel).intValue();
  827. if (intLevel >= -15 && intLevel < 16) {
  828. level = (byte)intLevel;
  829. if (embs == null) {
  830. embs = new byte[length];
  831. requiresBidi = true;
  832. if (!directionKnown) {
  833. directionKnown = true;
  834. isDirectionLTR = Bidi.defaultIsLTR(chars, 0, length);
  835. baselevel = (byte)(isDirectionLTR ? 0 : 1);
  836. }
  837. if (!isDirectionLTR) {
  838. for (int i = 0; i < pos - begin; ++i) {
  839. embs[i] = baselevel; // set initial level if rtl, already 0 so ok if ltr
  840. }
  841. }
  842. }
  843. }
  844. }
  845. catch (ClassCastException e) {
  846. }
  847. } else {
  848. if (embs != null) {
  849. level = baselevel;
  850. }
  851. }
  852. if (embs != null && level != 0) {
  853. for (int i = pos - begin; i < newpos - begin; ++i) {
  854. embs[i] = level;
  855. }
  856. }
  857. pos = newpos;
  858. } while (pos < end);
  859. if (!requiresBidi) {
  860. for (int i = 0; i < length; i++) {
  861. if (Bidi.requiresBidi(chars[i])) {
  862. requiresBidi = true;
  863. break;
  864. }
  865. }
  866. }
  867. if (requiresBidi) {
  868. if (!directionKnown) {
  869. isDirectionLTR = Bidi.defaultIsLTR(chars, 0, length);
  870. }
  871. if (embs == null) {
  872. embs = Bidi.getEmbeddingArray(chars, isDirectionLTR);
  873. }
  874. bidi = new Bidi(chars, embs, isDirectionLTR);
  875. }
  876. return bidi;
  877. }
  878. /**
  879. * Create a TextLine from the text. chars is just the text in the iterator.
  880. */
  881. public static TextLine standardCreateTextLine(FontRenderContext frc,
  882. AttributedCharacterIterator text,
  883. char[] chars,
  884. float[] baselineOffsets) {
  885. FontSource fontSource = new ACIFontSource(text);
  886. Bidi bidi = createBidiOnParagraph(text, chars);
  887. TextLabelFactory factory = new TextLabelFactory(frc, chars, bidi);
  888. boolean isDirectionLTR = true;
  889. if (bidi != null) {
  890. isDirectionLTR = bidi.isDirectionLTR();
  891. }
  892. return createLineFromText(chars, 0, chars.length, fontSource, factory, isDirectionLTR, baselineOffsets);
  893. }
  894. /*
  895. * A utility to get a range of text that is both logically and visually
  896. * contiguous.
  897. * If the entire range is ok, return limit, otherwise return the first
  898. * directional change after start. We could do better than this, but
  899. * it doesn't seem worth it at the moment.
  900. private static int firstVisualChunk(int order[], byte direction[],
  901. int start, int limit)
  902. {
  903. if (order != null) {
  904. int min = order[start];
  905. int max = order[start];
  906. int count = limit - start;
  907. for (int i = start + 1; i < limit; i++) {
  908. min = Math.min(min, order[i]);
  909. max = Math.max(max, order[i]);
  910. if (max - min >= count) {
  911. if (direction != null) {
  912. byte baseLevel = direction[start];
  913. for (int j = start + 1; j < i; j++) {
  914. if (direction[j] != baseLevel) {
  915. return j;
  916. }
  917. }
  918. }
  919. return i;
  920. }
  921. }
  922. }
  923. return limit;
  924. }
  925. */
  926. /*
  927. * The new version requires that chunks be at the same level.
  928. */
  929. private static int firstVisualChunk(int order[], byte direction[],
  930. int start, int limit)
  931. {
  932. if (order != null && direction != null) {
  933. byte dir = direction[start];
  934. while (++start < limit && direction[start] == dir) {}
  935. return start;
  936. }
  937. return limit;
  938. }
  939. /*
  940. * create a new line with characters between charStart and charLimit
  941. * justified using the provided width and ratio.
  942. */
  943. public TextLine getJustifiedLine(float justificationWidth, float justifyRatio, int justStart, int justLimit) {
  944. TextLineComponent[] newComponents = new TextLineComponent[fComponents.length];
  945. System.arraycopy(fComponents, 0, newComponents, 0, fComponents.length);
  946. float leftHang = 0;
  947. float adv = 0;
  948. float justifyDelta = 0;
  949. boolean rejustify = false;
  950. do {
  951. adv = getAdvanceBetween(newComponents, 0, characterCount());
  952. // all characters outside the justification range must be in the base direction
  953. // of the layout, otherwise justification makes no sense.
  954. float justifyAdvance = getAdvanceBetween(newComponents, justStart, justLimit);
  955. // get the actual justification delta
  956. justifyDelta = (justificationWidth - justifyAdvance) * justifyRatio;
  957. // generate an array of GlyphJustificationInfo records to pass to
  958. // the justifier. Array is visually ordered.
  959. // get positions that each component will be using
  960. int[] infoPositions = new int[newComponents.length];
  961. int infoCount = 0;
  962. for (int visIndex = 0; visIndex < newComponents.length; visIndex++) {
  963. int logIndex = fComponentVisualOrder == null ? visIndex : fComponentVisualOrder[visIndex];
  964. infoPositions[logIndex] = infoCount;
  965. infoCount += newComponents[logIndex].getNumJustificationInfos();
  966. }
  967. GlyphJustificationInfo[] infos = new GlyphJustificationInfo[infoCount];
  968. // get justification infos
  969. int compStart = 0;
  970. for (int i = 0; i < newComponents.length; i++) {
  971. TextLineComponent comp = newComponents[i];
  972. int compLength = comp.getNumCharacters();
  973. int compLimit = compStart + compLength;
  974. if (compLimit > justStart) {
  975. int rangeMin = Math.max(0, justStart - compStart);
  976. int rangeMax = Math.min(compLength, justLimit - compStart);
  977. comp.getJustificationInfos(infos, infoPositions[i], rangeMin, rangeMax);
  978. if (compLimit >= justLimit) {
  979. break;
  980. }
  981. }
  982. }
  983. // records are visually ordered, and contiguous, so start and end are
  984. // simply the places where we didn't fetch records
  985. int infoStart = 0;
  986. int infoLimit = infoCount;
  987. while (infoStart < infoLimit && infos[infoStart] == null) {
  988. ++infoStart;
  989. }
  990. while (infoLimit > infoStart && infos[infoLimit - 1] == null) {
  991. --infoLimit;
  992. }
  993. // invoke justifier on the records
  994. TextJustifier justifier = new TextJustifier(infos, infoStart, infoLimit);
  995. float[] deltas = justifier.justify(justifyDelta);
  996. boolean canRejustify = rejustify == false;
  997. boolean wantRejustify = false;
  998. boolean[] flags = new boolean[1];
  999. // apply justification deltas
  1000. compStart = 0;
  1001. for (int i = 0; i < newComponents.length; i++) {
  1002. TextLineComponent comp = newComponents[i];
  1003. int compLength = comp.getNumCharacters();
  1004. int compLimit = compStart + compLength;
  1005. if (compLimit > justStart) {
  1006. int rangeMin = Math.max(0, justStart - compStart);
  1007. int rangeMax = Math.min(compLength, justLimit - compStart);
  1008. newComponents[i] = comp.applyJustificationDeltas(deltas, infoPositions[i] * 2, flags);
  1009. wantRejustify |= flags[0];
  1010. if (compLimit >= justLimit) {
  1011. break;
  1012. }
  1013. }
  1014. }
  1015. rejustify = wantRejustify && !rejustify; // only make two passes
  1016. } while (rejustify);
  1017. return new TextLine(newComponents, fBaselineOffsets, fChars, fCharsStart,
  1018. fCharsLimit, fCharLogicalOrder, fCharLevels,
  1019. fIsDirectionLTR);
  1020. }
  1021. // return the sum of the advances of text between the logical start and limit
  1022. public static float getAdvanceBetween(TextLineComponent[] components, int start, int limit) {
  1023. float advance = 0;
  1024. int tlcStart = 0;
  1025. for(int i = 0; i < components.length; i++) {
  1026. TextLineComponent comp = components[i];
  1027. int tlcLength = comp.getNumCharacters();
  1028. int tlcLimit = tlcStart + tlcLength;
  1029. if (tlcLimit > start) {
  1030. int measureStart = Math.max(0, start - tlcStart);
  1031. int measureLimit = Math.min(tlcLength, limit - tlcStart);
  1032. advance += comp.getAdvanceBetween(measureStart, measureLimit);
  1033. if (tlcLimit >= limit) {
  1034. break;
  1035. }
  1036. }
  1037. tlcStart = tlcLimit;
  1038. }
  1039. return advance;
  1040. }
  1041. }