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