1. /*
  2. * @(#)StandardExtendedTextLabel.java 1.10 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * @(#)StandardExtendedTextLabel.java 1.10 01/11/29
  9. *
  10. * (C) Copyright IBM Corp. 1998, All Rights Reserved
  11. */
  12. package javax.swing.text;
  13. import java.awt.Font;
  14. import java.awt.Graphics2D;
  15. import java.awt.Shape;
  16. import java.awt.font.FontRenderContext;
  17. import java.awt.font.GlyphMetrics;
  18. import java.awt.font.GlyphVector;
  19. import java.awt.font.LineMetrics;
  20. import java.awt.geom.Point2D;
  21. import java.awt.geom.Rectangle2D;
  22. import java.awt.geom.AffineTransform;
  23. import sun.awt.font.NativeFontWrapper;
  24. import sun.awt.font.StandardGlyphVector;
  25. /**
  26. * An implementation of ExtendedTextLabel without using TextSource.
  27. */
  28. // !!! this implementation does not perform indic reordering
  29. class StandardExtendedTextLabel extends ExtendedTextLabel {
  30. GlyphVector gv;
  31. float[] charinfo;
  32. LineMetrics metrics;
  33. boolean dataIsLTR;
  34. boolean lineIsLTR;
  35. float italicAngle;
  36. int numchars;
  37. Rectangle2D lb;
  38. Rectangle2D ab;
  39. Rectangle2D vb;
  40. protected StandardExtendedTextLabel(
  41. GlyphVector gv,
  42. float[] charinfo,
  43. LineMetrics metrics,
  44. boolean dataIsLTR,
  45. boolean lineIsLTR,
  46. float italicAngle) {
  47. this.gv = gv;
  48. this.charinfo = charinfo;
  49. this.metrics = metrics;
  50. this.dataIsLTR = dataIsLTR;
  51. this.lineIsLTR = lineIsLTR;
  52. this.italicAngle = italicAngle;
  53. this.numchars = charinfo.length / numvals;
  54. }
  55. // TextLabel API
  56. Rectangle2D getLogicalBounds(float x, float y) {
  57. if (lb == null) {
  58. lb = createLogicalBounds();
  59. }
  60. return new Rectangle2D.Float((float)(lb.getX() + x),
  61. (float)(lb.getY() + y),
  62. (float)lb.getWidth(),
  63. (float)lb.getHeight());
  64. }
  65. Rectangle2D getVisualBounds(float x, float y) {
  66. if (vb == null) {
  67. vb = createVisualBounds();
  68. }
  69. return new Rectangle2D.Float((float)(vb.getX() + x),
  70. (float)(vb.getY() + y),
  71. (float)vb.getWidth(),
  72. (float)vb.getHeight());
  73. }
  74. Rectangle2D getAlignBounds(float x, float y) {
  75. if (ab == null) {
  76. ab = createAlignBounds();
  77. }
  78. return new Rectangle2D.Float((float)(ab.getX() + x),
  79. (float)(ab.getY() + y),
  80. (float)ab.getWidth(),
  81. (float)ab.getHeight());
  82. }
  83. Shape getOutline(float x, float y) {
  84. return gv.getOutline(x, y);
  85. }
  86. void draw(Graphics2D g, float x, float y) {
  87. g.drawGlyphVector(gv, x, y);
  88. }
  89. protected Rectangle2D createLogicalBounds() {
  90. // return gv.getLogicalBounds();
  91. // !!! current gv implementation is slow and incorrect
  92. // this one is just slow
  93. float ll = 0f;
  94. float lt = -metrics.getAscent();
  95. float lw = 0f;
  96. float lh = metrics.getAscent() + metrics.getDescent() + metrics.getLeading();
  97. int rn = charinfo.length - numvals;
  98. while (rn > 0) {
  99. if (charinfo[rn+advx] != 0) break;
  100. rn -= numvals;
  101. }
  102. if (rn >= 0) {
  103. int ln = 0;
  104. while (ln < rn) {
  105. if (charinfo[ln+advx] != 0) break;
  106. ln += numvals;
  107. }
  108. ll = Math.min(0f, charinfo[ln+posx]);
  109. lw = charinfo[rn+posx] + charinfo[rn+advx] - ll;
  110. }
  111. return new Rectangle2D.Float(ll, lt, lw, lh);
  112. }
  113. protected Rectangle2D createVisualBounds() {
  114. return gv.getVisualBounds();
  115. }
  116. // like createLogicalBounds except ignore leading and logically trailing white space
  117. // this assumes logically trailing whitespace is also visually trailing
  118. protected Rectangle2D createAlignBounds() {
  119. float al = 0f;
  120. float at = -metrics.getAscent();
  121. float aw = 0f;
  122. float ah = metrics.getAscent() + metrics.getDescent();
  123. int rn = charinfo.length - numvals;
  124. while (rn > 0 && ((charinfo[rn+advx] == 0) || (lineIsLTR && charinfo[rn+visw] == 0))) {
  125. rn -= numvals;
  126. }
  127. if (rn >= 0) {
  128. int ln = 0;
  129. while (ln < rn && ((charinfo[ln+advx] == 0) || (!lineIsLTR && charinfo[ln+visw] == 0))) {
  130. ln += numvals;
  131. }
  132. al = Math.max(0f, charinfo[ln+posx]);
  133. aw = charinfo[rn+posx] + charinfo[rn+advx] - al;
  134. }
  135. return new Rectangle2D.Float(al, at, aw, ah);
  136. }
  137. // ExtendedTextLabel API
  138. private static final int posx = 0,
  139. posy = 1,
  140. advx = 2,
  141. advy = 3,
  142. visx = 4,
  143. visy = 5,
  144. visw = 6,
  145. vish = 7;
  146. private static final int numvals = 8;
  147. int getNumCharacters() {
  148. return numchars;
  149. }
  150. float getItalicAngle() {
  151. return italicAngle;
  152. }
  153. LineMetrics getLineMetrics() {
  154. return metrics;
  155. }
  156. float getCharX(int index) {
  157. validate(index);
  158. return charinfo[l2v(index) * numvals + posx];
  159. }
  160. float getCharY(int index) {
  161. validate(index);
  162. return charinfo[l2v(index) * numvals + posy];
  163. }
  164. float getCharAdvance(int index) {
  165. validate(index);
  166. return charinfo[l2v(index) * numvals + advx];
  167. }
  168. Rectangle2D getCharVisualBounds(int index, float x, float y) {
  169. validate(index);
  170. index = l2v(index) * numvals;
  171. return new Rectangle2D.Float(
  172. charinfo[index + visx] + x,
  173. charinfo[index + visy] + y,
  174. charinfo[index + visw],
  175. charinfo[index + vish]);
  176. }
  177. int logicalToVisual(int logicalIndex) {
  178. validate(logicalIndex);
  179. return l2v(logicalIndex);
  180. }
  181. int visualToLogical(int visualIndex) {
  182. validate(visualIndex);
  183. return v2l(visualIndex);
  184. }
  185. int getLineBreakIndex(int start, float width) {
  186. validate(start);
  187. --start;
  188. while (width >= 0 && ++start < numchars) {
  189. width -= charinfo[l2v(start) * numvals + advx];
  190. }
  191. return start;
  192. }
  193. /*
  194. int getCharAdvanceAtWidth(int start, float width) {
  195. int index = start * numvals + advx;
  196. if( ltr ) {
  197. int numchars = getNumCharacters();
  198. --start;
  199. while( width >= 0 && ++start < numchars) {
  200. width -= charinfo[index];
  201. index += numvals;
  202. }
  203. } else {
  204. ++start;
  205. while( width >= 0 && --start >= 0 ) {
  206. width -= charinfo[index];
  207. index -= numvals;
  208. }
  209. }
  210. return start;
  211. }
  212. */
  213. int getCharIndexAtWidth(float width) {
  214. int start = 0;
  215. if( dataIsLTR ) {
  216. int index = start * numvals + advx;
  217. int numchars = getNumCharacters();
  218. --start;
  219. while( width >= 0 && ++start < numchars) {
  220. width -= charinfo[index];
  221. index += numvals;
  222. }
  223. } else {
  224. start = getNumCharacters()-1;
  225. int index = start * numvals + advx;
  226. ++start;
  227. while( width >= 0 && --start >= 0 ) {
  228. width -= charinfo[index];
  229. index -= numvals;
  230. }
  231. }
  232. //if( start >= getNumCharacters() )
  233. // System.out.println("getCharIndexAtWidth: " + start+", width "+width);
  234. return start;
  235. }
  236. float getCharAdvanceBetween(int start, int limit) {
  237. float a = 0f;
  238. --start;
  239. while (++start < limit) {
  240. a += charinfo[l2v(start) * numvals + advx];
  241. }
  242. return a;
  243. }
  244. private void validate(int index) {
  245. if (index < 0) {
  246. throw new IllegalArgumentException("index " + index + " < 0");
  247. } else if (index >= numchars) {
  248. throw new IllegalArgumentException("index " + index + " >= " + numchars);
  249. }
  250. }
  251. protected int l2v(int index) {
  252. return dataIsLTR ? index : numchars - 1 - index;
  253. }
  254. protected int v2l(int index) {
  255. return dataIsLTR ? index : numchars - 1 - index;
  256. }
  257. public String toString() {
  258. String result = null;
  259. for(int i=0; i<charinfo.length; i+=numvals) {
  260. result += "\tadvx = " + charinfo[i+advx];
  261. result += " advy = " + charinfo[i+advy];
  262. result += " posx = " + charinfo[i+posx];
  263. result += " posy = " + charinfo[i+posy];
  264. result += "\n";
  265. }
  266. return result;
  267. }
  268. static ExtendedTextLabel create(
  269. char[] context,
  270. int contextStart,
  271. int contextLength,
  272. int start,
  273. int length,
  274. boolean ltr,
  275. Font font,
  276. FontRenderContext frc) {
  277. char[] glyphs = context;
  278. int gStart = start;
  279. // Only do arabic processing on rtl data
  280. if (ltr) {
  281. for (int i = start, e = start + length; i < e; ++i) {
  282. if (isFormatMark(context[i])) {
  283. glyphs = new char[length];
  284. gStart = 0;
  285. System.arraycopy(context, start, glyphs, 0, length);
  286. glyphs[i-start] = '\uffff';
  287. for (int j = i - start + 1; j < length; ++j) {
  288. if (isFormatMark(glyphs[j])) {
  289. glyphs[j] = '\uffff';
  290. }
  291. }
  292. break;
  293. }
  294. }
  295. } else {
  296. // get shape type of char on visual right
  297. // !!! assume no indic for now
  298. int rightType = NewArabicShaping.VALUE_NOSHAPE_NONE;
  299. for (int i = start - 1; i >= contextStart; --i) {
  300. rightType = NewArabicShaping.getShapeType(context[i]);
  301. if (rightType != NewArabicShaping.VALUE_TRANSPARENT) {
  302. break;
  303. }
  304. }
  305. // get shape type of char on visual left
  306. int leftType = NewArabicShaping.VALUE_NOSHAPE_NONE;
  307. for (int i = start + length, e = contextStart + contextLength; i < e; ++i) {
  308. leftType = NewArabicShaping.getShapeType(context[i]);
  309. if (leftType != NewArabicShaping.VALUE_TRANSPARENT) {
  310. break;
  311. }
  312. }
  313. gStart = 0;
  314. glyphs = new char[length];
  315. for (int i = length, j = start - 1; i > 0;) {
  316. glyphs[--i] = context[++j];
  317. }
  318. NewArabicShaping.shape(glyphs, leftType, rightType);
  319. ArabicLigaturizer.getLamAlefInstance().ligaturize(glyphs, 0, glyphs.length);
  320. // hack mirroring, replace format marks
  321. for (int i = 0; i < length; ++i) {
  322. char c = glyphs[i];
  323. if (isFormatMark(c)) {
  324. glyphs[i] = '\uffff';
  325. } else {
  326. glyphs[i] = getMirroredChar(c);
  327. }
  328. }
  329. }
  330. StandardGlyphVector gv = new StandardGlyphVector(font, glyphs, gStart, length, frc);
  331. // create the charinfo, assume 1-1 chars to glyphs
  332. float[] charinfo = gv.getGlyphInfo();
  333. // create the metrics, ignore dropped chars, !!! linemetrics length is bogus
  334. LineMetrics metrics = font.getLineMetrics(glyphs, gStart, length, frc);
  335. // default lineIsLTR to dataIsLTR
  336. boolean lineIsLTR = ltr;
  337. boolean dataIsLTR = ltr;
  338. float italicAngle = font.getItalicAngle();
  339. return new StandardExtendedTextLabel(gv, charinfo, metrics, dataIsLTR, lineIsLTR, italicAngle);
  340. }
  341. private static boolean isFormatMark(char c) {
  342. // this includes not only the format marks, but also tab, cr, lf, ps, and ls.
  343. // the compiler may not help us out here, this might be worth optimizing, if
  344. // everything else gets cleaned up.
  345. switch (c) {
  346. case '\u0009':
  347. case '\012': // aka u+000a, but compiler complains about premature eol
  348. case '\015': // aka u+000d, same problem
  349. case '\u200c':
  350. case '\u200d':
  351. case '\u200e':
  352. case '\u200f':
  353. case '\u2028':
  354. case '\u2029':
  355. case '\u202a':
  356. case '\u202b':
  357. case '\u202c':
  358. case '\u202d':
  359. case '\u202e':
  360. case '\u206a':
  361. case '\u206b':
  362. case '\u206c':
  363. case '\u206d':
  364. case '\u206e':
  365. case '\u206f':
  366. case '\ufeff':
  367. return true;
  368. default:
  369. return false;
  370. }
  371. }
  372. // Not the same as mirroring, which is a glyph feature. The best we can do is swap
  373. // with a paired character when there is one. This won't position the character
  374. // properly, but it is a reasonable approximation.
  375. private static final char[] mirrorPairs = {
  376. '\u0028', '\u0029', // ascii paired punctuation
  377. '\u003c', '\u003e',
  378. '\u005b', '\u005d',
  379. '\u007b', '\u007d',
  380. '\u2045', '\u2046', // math symbols (not complete)
  381. '\u207d', '\u207e',
  382. '\u208d', '\u208e',
  383. '\u2264', '\u2265',
  384. '\u3008', '\u3009', // chinese paired punctuation
  385. '\u300a', '\u300b',
  386. '\u300c', '\u300d',
  387. '\u300e', '\u300f',
  388. '\u3010', '\u3011',
  389. '\u3014', '\u3015',
  390. '\u3016', '\u3017',
  391. '\u3018', '\u3019',
  392. '\u301a', '\u301b',
  393. };
  394. private static char getMirroredChar(char c) {
  395. if (c <= '\u007d' || (c >= '\u2045' && c <= '\u301b')) {
  396. for (int i = 0; i < mirrorPairs.length; i++) {
  397. char mc = mirrorPairs[i];
  398. if (mc == c) {
  399. return mirrorPairs[i + (((i & 0x1) == 0) ? 1 : -1)];
  400. } else if (mc > c) {
  401. break;
  402. }
  403. }
  404. }
  405. return c;
  406. }
  407. /*
  408. static ExtendedTextLabel create(
  409. char[] context,
  410. int contextStart,
  411. int contextLength,
  412. int start,
  413. int length,
  414. boolean ltr,
  415. Font font,
  416. FontRenderContext frc) {
  417. //System.out.println("ExtendedTextLabel: context["+new String(context,contextStart,contextLength)+"]");
  418. //System.out.println("\tcontextStart = " + contextStart +
  419. // " contextLength = " + contextLength);
  420. //System.out.println("\tstart = " + start + " length = " + length +
  421. // " ltr = " + ltr);
  422. int leftType = ArabicShaping.NEITHER;
  423. for (int i = start - 1; i >= contextStart; --i) {
  424. leftType = ArabicShaping.getShapeType(context[i]);
  425. if (leftType != ArabicShaping.TRANSPARENT) {
  426. break;
  427. }
  428. }
  429. int rightType = ArabicShaping.NEITHER;
  430. for (int i = start + length, e = contextStart + contextLength; i < e; ++i) {
  431. rightType = ArabicShaping.getShapeType(context[i]);
  432. if (rightType != ArabicShaping.TRANSPARENT) {
  433. break;
  434. }
  435. }
  436. if (!ltr) {
  437. int tempType = leftType;
  438. leftType = rightType;
  439. rightType = tempType;
  440. }
  441. char[] chars = new char[length];
  442. System.arraycopy(context, start, chars, 0, length);
  443. LineMetrics lm = font.getLineMetrics(context, start, length, frc);
  444. frc = new FontRenderContext(null,false,false);
  445. char[] glyphs = new char[length];
  446. if (ltr) {
  447. System.arraycopy(chars, 0, glyphs, 0, length);
  448. } else {
  449. for (int i = length, j = start - 1; i > 0;) {
  450. glyphs[--i] = ArabicShaping.getMirroredChar(context[++j]); // hack mirrors
  451. }
  452. }
  453. ArabicShaping.shapeArabic(glyphs, leftType, rightType, '\uffff');
  454. boolean[] ligmap = new boolean[length];
  455. int glength = -1;
  456. for (int i = 0; i < length; i++) {
  457. char c = glyphs[i];
  458. if (c == '\uffff' || c < '\u0020') { // hide all C0 control chars by making them behave like ligatures
  459. ligmap[i] = true;
  460. }
  461. else {
  462. glyphs[++glength] = c;
  463. }
  464. }
  465. ++glength;
  466. char[] nglyphs = new char[glength];
  467. System.arraycopy(glyphs, 0, nglyphs, 0, glength);
  468. glyphs = nglyphs;
  469. // for (int i = 0; i < glyphs.length; i++) {
  470. // System.out.print(Integer.toHexString(glyphs[i]) + " ");
  471. // }
  472. // System.out.println();
  473. GlyphVector gv = font.createGlyphVector(frc, glyphs);
  474. gv.performDefaultLayout();
  475. //System.out.println(frc.getTransform());
  476. //System.out.println(font.getTransform());
  477. float[] info = new float[length * numvals];
  478. float[] xy = gv.getGlyphPositions(0, glength, null);
  479. if (ltr) {
  480. float x = 0f;
  481. float y = 0f;
  482. float a = 0f;
  483. for (int i = 0, n = 0, gi = 0, gn = 0; i < length; i++, n += numvals) {
  484. if (ligmap[i]) {
  485. info[n+posx] = x + a;
  486. } else {
  487. GlyphMetrics gm = gv.getGlyphMetrics(gi++);
  488. x = xy[gn++];
  489. y = xy[gn++];
  490. a = gm.getAdvance();
  491. info[n+advx] = a;
  492. info[n+advy] = 0;
  493. info[n+posx] = x;
  494. info[n+posy] = y;
  495. Rectangle2D r = gm.getBounds2D();
  496. // System.out.println("gm " + i + " bounds: " + r);
  497. info[n+visx] = (float)(x + r.getX());
  498. info[n+visy] = (float)(y + r.getY());
  499. info[n+visw] = (float)r.getWidth();
  500. info[n+vish] = (float)r.getHeight();
  501. }
  502. }
  503. } else {
  504. float x = glength > 0 ? xy[xy.length - 2] + gv.getGlyphMetrics(glength - 1).getAdvance() : 0f;
  505. float y = 0f;
  506. float a = 0f;
  507. for (int i = length, n = 0, gi = glength, gn = xy.length; --i >= 0; n += numvals) {
  508. if (ligmap[i]) {
  509. info[n+posx] = x;
  510. } else {
  511. GlyphMetrics gm = gv.getGlyphMetrics(--gi);
  512. y = xy[--gn];
  513. x = xy[--gn];
  514. a = gm.getAdvance();
  515. info[n+advx] = a;
  516. info[n+advy] = 0;
  517. info[n+posx] = x;
  518. info[n+posy] = y;
  519. Rectangle2D r = gm.getBounds2D();
  520. // System.out.println("gm " + i + " bounds: " + r);
  521. info[n+visx] = (float)(x + r.getX());
  522. info[n+visy] = (float)(y + r.getY());
  523. info[n+visw] = (float)r.getWidth();
  524. info[n+vish] = (float)r.getHeight();
  525. }
  526. }
  527. }
  528. float al = 0f;
  529. float at = -metrics.getAscent();
  530. float aw = 0f;
  531. float ah = metrics.getAscent() + metrics.getDescent();
  532. float ll = al;
  533. float lt = at;
  534. float lw = aw;
  535. float lh = ah + metrics.getLeading();
  536. // System.out.println(metrics.getAscent() + ", " + metrics.getDescent() + ", " + metrics.getLeading());
  537. boolean foundlb = false;
  538. int n = length * numvals;
  539. while (n > 0) {
  540. n -= numvals;
  541. // System.out.println("info " + (n / numvals) + " adv: " + info[n+advx] + " visw: " + info[n+visw]);
  542. if (info[n+advx] != 0) {
  543. if (!foundlb) {
  544. foundlb = true;
  545. if (ltr) {
  546. ll = Math.min(0f, info[posx]);
  547. lw = info[n+posx] + info[n+advx] - ll;
  548. } else {
  549. ll = Math.min(0f, info[n+posx]);
  550. lw = info[posx] + info[advx] - ll;
  551. }
  552. }
  553. if (info[n+visw] != 0) { // replaces Character.isSpaceChar
  554. if (ltr) {
  555. al = Math.max(0f, info[posx]);
  556. aw = info[n+posx]+info[n+advx] - al;
  557. } else {
  558. al = Math.max(0f, info[n+posx]);
  559. aw = info[posx]+info[advx] - al;
  560. }
  561. break;
  562. }
  563. }
  564. }
  565. Rectangle2D ab = new Rectangle2D.Float(al, at, aw, ah);
  566. Rectangle2D lb = new Rectangle2D.Float(ll, lt, lw, lh);
  567. TextLabel label = new StandardTextLabel(gv, lb, ab);
  568. ExtendedTextLabel extLabel = new StandardExtendedTextLabel(label, lm, info, chars, ltr, font, frc);
  569. return extLabel;
  570. }
  571. */
  572. }