/jEdit/tags/jedit-4-4-2/org/gjt/sp/jedit/syntax/Chunk.java

#
Java | 588 lines | 351 code | 66 blank | 171 comment | 71 complexity | e74c3247643a0e47e00d36175c376d14 MD5 | raw file

✨ Summary
  1. /*
  2. * Chunk.java - A syntax token with extra information required for painting it
  3. * on screen
  4. * :tabSize=8:indentSize=8:noTabs=false:
  5. * :folding=explicit:collapseFolds=1:
  6. *
  7. * Copyright (C) 2001, 2002 Slava Pestov
  8. *
  9. * This program is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation; either version 2
  12. * of the License, or any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program; if not, write to the Free Software
  21. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  22. */
  23. package org.gjt.sp.jedit.syntax;
  24. //{{{ Imports
  25. import javax.swing.text.*;
  26. import java.awt.font.*;
  27. import java.awt.geom.*;
  28. import java.awt.*;
  29. import java.util.ArrayList;
  30. import java.util.LinkedList;
  31. import java.util.List;
  32. import org.gjt.sp.jedit.Debug;
  33. import org.gjt.sp.jedit.IPropertyManager;
  34. //}}}
  35. /**
  36. * A syntax token with extra information required for painting it
  37. * on screen.
  38. * @since jEdit 4.1pre1
  39. */
  40. public class Chunk extends Token
  41. {
  42. //{{{ Static variables
  43. private static final char[] EMPTY_TEXT = new char[0];
  44. //}}}
  45. //{{{ paintChunkList() method
  46. /**
  47. * Paints a chunk list.
  48. * @param chunks The chunk list
  49. * @param gfx The graphics context
  50. * @param x The x co-ordinate
  51. * @param y The y co-ordinate
  52. * @param glyphVector true if we want to use glyphVector, false if we
  53. * want to use drawString
  54. * @return The width of the painted text
  55. * @since jEdit 4.2pre1
  56. */
  57. public static float paintChunkList(Chunk chunks,
  58. Graphics2D gfx, float x, float y, boolean glyphVector)
  59. {
  60. Rectangle clipRect = gfx.getClipBounds();
  61. float _x = 0.0f;
  62. while(chunks != null)
  63. {
  64. // only paint visible chunks
  65. if(x + _x + chunks.width > clipRect.x
  66. && x + _x < clipRect.x + clipRect.width)
  67. {
  68. // Useful for debugging purposes
  69. if(Debug.CHUNK_PAINT_DEBUG)
  70. {
  71. gfx.draw(new Rectangle2D.Float(x + _x,y - 10,
  72. chunks.width,10));
  73. }
  74. if(chunks.accessable && chunks.visible)
  75. {
  76. gfx.setFont(chunks.style.getFont());
  77. gfx.setColor(chunks.style.getForegroundColor());
  78. if (glyphVector && chunks.glyphs != null)
  79. chunks.drawGlyphs(gfx, x + _x, y);
  80. else if(chunks.str != null)
  81. {
  82. gfx.drawString(chunks.str,
  83. (int)(x + _x),(int)y);
  84. }
  85. }
  86. }
  87. _x += chunks.width;
  88. chunks = (Chunk)chunks.next;
  89. }
  90. return _x;
  91. } //}}}
  92. //{{{ paintChunkBackgrounds() method
  93. /**
  94. * Paints the background highlights of a chunk list.
  95. * @param chunks The chunk list
  96. * @param gfx The graphics context
  97. * @param x The x co-ordinate
  98. * @param y The y co-ordinate
  99. * @return The width of the painted backgrounds
  100. * @since jEdit 4.2pre1
  101. */
  102. public static float paintChunkBackgrounds(Chunk chunks,
  103. Graphics2D gfx, float x, float y)
  104. {
  105. Rectangle clipRect = gfx.getClipBounds();
  106. float _x = 0.0f;
  107. FontMetrics forBackground = gfx.getFontMetrics();
  108. int ascent = forBackground.getAscent();
  109. int height = forBackground.getHeight();
  110. while(chunks != null)
  111. {
  112. // only paint visible chunks
  113. if(x + _x + chunks.width > clipRect.x
  114. && x + _x < clipRect.x + clipRect.width)
  115. {
  116. if(chunks.accessable)
  117. {
  118. //{{{ Paint token background color if necessary
  119. Color bgColor = chunks.background;
  120. if(bgColor != null)
  121. {
  122. gfx.setColor(bgColor);
  123. gfx.fill(new Rectangle2D.Float(
  124. x + _x,y - ascent,
  125. _x + chunks.width - _x,
  126. height));
  127. } //}}}
  128. }
  129. }
  130. _x += chunks.width;
  131. chunks = (Chunk)chunks.next;
  132. }
  133. return _x;
  134. } //}}}
  135. //{{{ offsetToX() method
  136. /**
  137. * Converts an offset in a chunk list into an x co-ordinate.
  138. * @param chunks The chunk list
  139. * @param offset The offset
  140. * @since jEdit 4.1pre1
  141. */
  142. public static float offsetToX(Chunk chunks, int offset)
  143. {
  144. if(chunks != null && offset < chunks.offset)
  145. {
  146. throw new ArrayIndexOutOfBoundsException(offset + " < "
  147. + chunks.offset);
  148. }
  149. float x = 0.0f;
  150. while(chunks != null)
  151. {
  152. if(chunks.accessable && offset < chunks.offset + chunks.length)
  153. return x + chunks.offsetToX(offset - chunks.offset);
  154. x += chunks.width;
  155. chunks = (Chunk)chunks.next;
  156. }
  157. return x;
  158. } //}}}
  159. //{{{ xToOffset() method
  160. /**
  161. * Converts an x co-ordinate in a chunk list into an offset.
  162. * @param chunks The chunk list
  163. * @param x The x co-ordinate
  164. * @param round Round up to next letter if past the middle of a letter?
  165. * @return The offset within the line, or -1 if the x co-ordinate is too
  166. * far to the right
  167. * @since jEdit 4.1pre1
  168. */
  169. public static int xToOffset(Chunk chunks, float x, boolean round)
  170. {
  171. float _x = 0.0f;
  172. while(chunks != null)
  173. {
  174. if(chunks.accessable && x < _x + chunks.width)
  175. return chunks.xToOffset(x - _x,round);
  176. _x += chunks.width;
  177. chunks = (Chunk)chunks.next;
  178. }
  179. return -1;
  180. } //}}}
  181. //{{{ propertiesChanged() method
  182. /**
  183. * Reload internal configuration based on the given properties.
  184. *
  185. * @param props Configuration properties.
  186. *
  187. * @since jEdit 4.4pre1
  188. */
  189. public static void propertiesChanged(IPropertyManager props)
  190. {
  191. fontSubstList = null;
  192. if (props == null)
  193. {
  194. fontSubstEnabled = false;
  195. preferredFonts = null;
  196. }
  197. else
  198. {
  199. fontSubstEnabled = Boolean.parseBoolean(props.getProperty("view.enableFontSubst"));
  200. }
  201. int i = 0;
  202. String family;
  203. List<Font> userFonts = new ArrayList<Font>();
  204. while ((family = props.getProperty("view.fontSubstList." + i)) != null)
  205. {
  206. /*
  207. * The default font is Font.DIALOG if the family
  208. * doesn't match any installed fonts. The following
  209. * check skips fonts that don't exist.
  210. */
  211. Font f = new Font(family, Font.PLAIN, 12);
  212. if (!f.getFamily().equalsIgnoreCase("dialog") ||
  213. family.equalsIgnoreCase("dialog"))
  214. userFonts.add(f);
  215. i++;
  216. }
  217. preferredFonts = userFonts.toArray(new Font[userFonts.size()]);
  218. } //}}}
  219. //{{{ Instance variables
  220. public boolean accessable;
  221. public boolean initialized;
  222. // set up after init()
  223. public SyntaxStyle style;
  224. public float width;
  225. //}}}
  226. //{{{ Chunk constructor
  227. public Chunk(float width, int offset, ParserRuleSet rules)
  228. {
  229. super(Token.NULL,offset,0,rules);
  230. this.width = width;
  231. } //}}}
  232. //{{{ Chunk constructor
  233. public Chunk(byte id, int offset, int length, ParserRuleSet rules,
  234. SyntaxStyle[] styles, byte defaultID)
  235. {
  236. super(id,offset,length,rules);
  237. accessable = true;
  238. style = styles[id];
  239. background = style.getBackgroundColor();
  240. if(background == null)
  241. background = styles[defaultID].getBackgroundColor();
  242. } //}}}
  243. //{{{ offsetToX() method
  244. public final float offsetToX(int offset)
  245. {
  246. if(!visible || glyphs == null)
  247. return 0.0f;
  248. float x = 0.0f;
  249. for (GlyphVector gv : glyphs)
  250. {
  251. if (offset < gv.getNumGlyphs())
  252. {
  253. x += (float) gv.getGlyphPosition(offset).getX();
  254. return x;
  255. }
  256. x += (float) gv.getLogicalBounds().getWidth();
  257. offset -= gv.getNumGlyphs();
  258. }
  259. /* Shouldn't reach this. */
  260. assert false : "Shouldn't reach this.";
  261. return -1;
  262. } //}}}
  263. //{{{ xToOffset() method
  264. public final int xToOffset(float x, boolean round)
  265. {
  266. if (!visible || glyphs == null)
  267. {
  268. if (round && width - x < x)
  269. return offset + length;
  270. else
  271. return offset;
  272. }
  273. int off = offset;
  274. float myx = 0.0f;
  275. for (GlyphVector gv : glyphs)
  276. {
  277. float gwidth = (float) gv.getLogicalBounds().getWidth();
  278. if (myx + gwidth >= x)
  279. {
  280. float[] pos = gv.getGlyphPositions(0, gv.getNumGlyphs(), null);
  281. for (int i = 0; i < gv.getNumGlyphs(); i++)
  282. {
  283. float glyphX = myx + pos[i * 2];
  284. float nextX = (i == gv.getNumGlyphs() - 1)
  285. ? width
  286. : myx + pos[i * 2 + 2];
  287. if (nextX > x)
  288. {
  289. if (!round || nextX - x > x - glyphX)
  290. return off + i;
  291. else
  292. return off + i + 1;
  293. }
  294. }
  295. }
  296. myx += gwidth;
  297. off += gv.getNumGlyphs();
  298. }
  299. /* Shouldn't reach this. */
  300. assert false : "Shouldn't reach this.";
  301. return -1;
  302. } //}}}
  303. //{{{ init() method
  304. public void init(Segment seg, TabExpander expander, float x,
  305. FontRenderContext fontRenderContext)
  306. {
  307. initialized = true;
  308. if(!accessable)
  309. {
  310. // do nothing
  311. }
  312. else if(length == 1 && seg.array[seg.offset + offset] == '\t')
  313. {
  314. visible = false;
  315. float newX = expander.nextTabStop(x,offset + length);
  316. width = newX - x;
  317. }
  318. else
  319. {
  320. visible = true;
  321. str = new String(seg.array,seg.offset + offset,length);
  322. char[] textArray = seg.array;
  323. int textStart = seg.offset + offset;
  324. width = layoutGlyphs(fontRenderContext,
  325. textArray,
  326. textStart,
  327. textStart + length);
  328. }
  329. } //}}}
  330. //{{{ Private members
  331. // this is either style.getBackgroundColor() or
  332. // styles[defaultID].getBackgroundColor()
  333. private Color background;
  334. private String str;
  335. //private GlyphVector gv;
  336. private List<GlyphVector> glyphs;
  337. private boolean visible;
  338. private static boolean fontSubstEnabled;
  339. private static Font[] preferredFonts;
  340. private static Font[] fontSubstList;
  341. //{{{ getFonts() method
  342. /**
  343. * Returns a list of fonts to be searched when applying font
  344. * substitution.
  345. */
  346. private static Font[] getFonts()
  347. {
  348. if (fontSubstList == null)
  349. {
  350. Font[] systemFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
  351. fontSubstList = new Font[preferredFonts.length +
  352. systemFonts.length];
  353. System.arraycopy(preferredFonts, 0, fontSubstList, 0,
  354. preferredFonts.length);
  355. System.arraycopy(systemFonts, 0, fontSubstList,
  356. preferredFonts.length,
  357. systemFonts.length);
  358. }
  359. return fontSubstList;
  360. } //}}}
  361. //{{{ drawGlyphs() method
  362. /**
  363. * Draws the internal list of glyph vectors into the given
  364. * graphics object.
  365. *
  366. * @param gfx Where to draw the glyphs.
  367. * @param x Starting horizontal position.
  368. * @param y Vertical position.
  369. */
  370. private void drawGlyphs(Graphics2D gfx,
  371. float x,
  372. float y)
  373. {
  374. for (GlyphVector gv : glyphs)
  375. {
  376. gfx.drawGlyphVector(gv, x, y);
  377. x += (float) gv.getLogicalBounds().getWidth();
  378. }
  379. } //}}}
  380. //{{{ addGlyphVector() method
  381. /**
  382. * Creates a glyph vector for the text with the given font,
  383. * and adds it to the internal list.
  384. *
  385. * @param f Font to use for rendering.
  386. * @param frc Font rendering context.
  387. * @param text Char array with text to render.
  388. * @param start Start index of text to render.
  389. * @param end End index of text to render.
  390. *
  391. * @return Width of the rendered text.
  392. */
  393. private float addGlyphVector(Font f,
  394. FontRenderContext frc,
  395. char[] text,
  396. int start,
  397. int end)
  398. {
  399. // FIXME: Need BiDi support.
  400. int layoutFlags = Font.LAYOUT_LEFT_TO_RIGHT
  401. | Font.LAYOUT_NO_START_CONTEXT
  402. | Font.LAYOUT_NO_LIMIT_CONTEXT;
  403. GlyphVector gv = f.layoutGlyphVector(frc,
  404. text,
  405. start,
  406. end,
  407. layoutFlags);
  408. // This is necessary to work around a memory leak in Sun Java 6 where
  409. // the sun.font.GlyphLayout is cached and reused while holding an
  410. // instance to the char array.
  411. f.layoutGlyphVector(frc, EMPTY_TEXT, 0, 0, layoutFlags);
  412. glyphs.add(gv);
  413. return (float) gv.getLogicalBounds().getWidth();
  414. } // }}}
  415. //{{{ layoutGlyphs() method
  416. /**
  417. * Layout the glyphs to render the given text, applying font
  418. * substitution if configured. GlyphVectors are created and
  419. * added to the internal glyph list.
  420. *
  421. * Font substitution works in the following manner:
  422. * - All characters that can be rendered with the default
  423. * font will be.
  424. * - For characters that can't be handled by the default
  425. * font, iterate over the list of available fonts to
  426. * find an appropriate one. The first match is chosen.
  427. *
  428. * The user can define his list of preferred fonts, which will
  429. * be tried before the system fonts.
  430. *
  431. * @param frc Font rendering context.
  432. * @param text Char array with text to render.
  433. * @param start Start index of text to render.
  434. * @param end End index of text to render.
  435. *
  436. * @return Width of the rendered text.
  437. */
  438. private float layoutGlyphs(FontRenderContext frc,
  439. char text[],
  440. int start,
  441. int end)
  442. {
  443. float width = 0.0f;
  444. int max = 0;
  445. Font dflt = style.getFont();
  446. glyphs = new LinkedList<GlyphVector>();
  447. while (max != -1 && start < end)
  448. {
  449. max = fontSubstEnabled ? dflt.canDisplayUpTo(text, start, end)
  450. : -1;
  451. if (max == -1)
  452. {
  453. width += addGlyphVector(dflt,
  454. frc,
  455. text,
  456. start,
  457. end);
  458. }
  459. else
  460. {
  461. /*
  462. * Draw as much as we can and update the
  463. * current offset.
  464. */
  465. if (max > start)
  466. {
  467. width += addGlyphVector(dflt,
  468. frc,
  469. text,
  470. start,
  471. max);
  472. start = max;
  473. }
  474. /*
  475. * Find a font that can display the next
  476. * characters.
  477. */
  478. Font f = null;
  479. for (Font candidate : getFonts())
  480. {
  481. if (candidate.canDisplay(text[start]))
  482. {
  483. f = candidate;
  484. break;
  485. }
  486. }
  487. if (f != null)
  488. {
  489. f = f.deriveFont(dflt.getStyle(), dflt.getSize());
  490. /*
  491. * Find out how many characters
  492. * the current font cannot
  493. * display, but the chosen one
  494. * can.
  495. */
  496. int last = start;
  497. while (last < end &&
  498. f.canDisplay(text[last]) &&
  499. !dflt.canDisplay(text[last]))
  500. last++;
  501. width += addGlyphVector(f,
  502. frc,
  503. text,
  504. start,
  505. last);
  506. start = last;
  507. }
  508. else
  509. {
  510. width += addGlyphVector(dflt,
  511. frc,
  512. text,
  513. start,
  514. start + 1);
  515. start++;
  516. }
  517. }
  518. }
  519. return width;
  520. } //}}}
  521. //}}}
  522. }