You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BasicTextLineRenderer.java 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.addons.ui_swing.textpane;
  18. import com.dmdirc.ui.messages.CachingDocument;
  19. import com.dmdirc.ui.messages.LinePosition;
  20. import java.awt.Color;
  21. import java.awt.Graphics2D;
  22. import java.awt.Shape;
  23. import java.awt.font.LineBreakMeasurer;
  24. import java.awt.font.TextAttribute;
  25. import java.awt.font.TextLayout;
  26. import java.awt.geom.Rectangle2D;
  27. import java.text.AttributedCharacterIterator;
  28. import java.text.AttributedString;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import javax.swing.UIManager;
  32. import static com.google.common.base.Preconditions.checkNotNull;
  33. /**
  34. * Renders basic text, line wrapping where appropriate.
  35. */
  36. public class BasicTextLineRenderer implements LineRenderer {
  37. /** Single Side padding for textpane. */
  38. private static final int SINGLE_SIDE_PADDING = 3;
  39. /** Both Side padding for textpane. */
  40. private static final int DOUBLE_SIDE_PADDING = SINGLE_SIDE_PADDING * 2;
  41. /** Render result to use. This instance is recycled for each render call. */
  42. private final RenderResult result = new RenderResult();
  43. private final TextPane textPane;
  44. private final TextPaneCanvas textPaneCanvas;
  45. private final CachingDocument<AttributedString> document;
  46. private final Color highlightForeground;
  47. private final Color highlightBackground;
  48. /** Reused in each render pass to save creating a new list. */
  49. private final List<TextLayout> wrappedLines = new ArrayList<>();
  50. public BasicTextLineRenderer(final TextPane textPane, final TextPaneCanvas textPaneCanvas,
  51. final CachingDocument<AttributedString> document) {
  52. this.textPane = textPane;
  53. this.textPaneCanvas = textPaneCanvas;
  54. this.document = document;
  55. highlightForeground = UIManager.getColor("TextArea.selectionForeground");
  56. highlightBackground = UIManager.getColor("TextArea.selectionBackground");
  57. }
  58. @Override
  59. public RenderResult render(final Graphics2D graphics, final float canvasWidth,
  60. final float canvasHeight, final float drawPosY, final int line) {
  61. result.drawnAreas.clear();
  62. result.textLayouts.clear();
  63. result.totalHeight = 0;
  64. final AttributedCharacterIterator iterator = document.getStyledLine(line).getIterator();
  65. final int paragraphStart = iterator.getBeginIndex();
  66. final int paragraphEnd = iterator.getEndIndex();
  67. final LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(iterator,
  68. graphics.getFontRenderContext());
  69. lineMeasurer.setPosition(paragraphStart);
  70. float newDrawPosY = drawPosY;
  71. // Calculate layouts for each wrapped line.
  72. wrappedLines.clear();
  73. int chars = 0;
  74. while (lineMeasurer.getPosition() < paragraphEnd) {
  75. final TextLayout layout = checkNotNull(lineMeasurer.nextLayout(canvasWidth));
  76. chars += layout.getCharacterCount();
  77. wrappedLines.add(layout);
  78. }
  79. // Loop through each wrapped line
  80. for (int i = wrappedLines.size() - 1; i >= 0; i--) {
  81. final TextLayout layout = wrappedLines.get(i);
  82. // Calculate the initial X position
  83. final float drawPosX;
  84. if (layout.isLeftToRight()) {
  85. drawPosX = SINGLE_SIDE_PADDING;
  86. } else {
  87. drawPosX = canvasWidth - layout.getAdvance();
  88. }
  89. chars -= layout.getCharacterCount();
  90. // Check if the target is in range
  91. if (newDrawPosY >= 0 || newDrawPosY <= canvasHeight) {
  92. renderLine(graphics, canvasWidth, line, drawPosX, newDrawPosY, i, chars,
  93. layout);
  94. }
  95. // Calculate the Y offset
  96. newDrawPosY -= layout.getAscent() + layout.getLeading() + layout.getDescent();
  97. }
  98. result.totalHeight = drawPosY - newDrawPosY;
  99. return result;
  100. }
  101. protected void renderLine(final Graphics2D graphics, final float canvasWidth, final int line,
  102. final float drawPosX, final float drawPosY, final int numberOfWraps, final int chars,
  103. final TextLayout layout) {
  104. graphics.setColor(textPane.getForeground());
  105. layout.draw(graphics, drawPosX, drawPosY);
  106. doHighlight(line, chars, layout, graphics, canvasWidth + DOUBLE_SIDE_PADDING,
  107. drawPosX, drawPosY);
  108. final LineInfo lineInfo = new LineInfo(line, numberOfWraps);
  109. result.firstVisibleLine = line;
  110. result.textLayouts.put(lineInfo, layout);
  111. result.drawnAreas.put(lineInfo,
  112. new Rectangle2D.Float(0,
  113. drawPosY - layout.getAscent() - layout.getLeading(),
  114. canvasWidth + DOUBLE_SIDE_PADDING,
  115. layout.getAscent() + layout.getDescent() + layout.getLeading()));
  116. }
  117. /**
  118. * Redraws the text that has been highlighted.
  119. *
  120. * @param line Line number
  121. * @param chars Number of characters already handled in a wrapped line
  122. * @param layout Current wrapped line's textlayout
  123. * @param g Graphics surface to draw highlight on
  124. * @param drawPosX current x location of the line
  125. * @param drawPosY current y location of the line
  126. */
  127. protected void doHighlight(final int line, final int chars,
  128. final TextLayout layout, final Graphics2D g, final float canvasWidth,
  129. final float drawPosX, final float drawPosY) {
  130. final LinePosition selectedRange = textPaneCanvas.getSelectedRange();
  131. final int selectionStartLine = selectedRange.getStartLine();
  132. final int selectionStartChar = selectedRange.getStartPos();
  133. final int selectionEndLine = selectedRange.getEndLine();
  134. final int selectionEndChar = selectedRange.getEndPos();
  135. // Does this line need highlighting?
  136. if (selectionStartLine <= line && selectionEndLine >= line) {
  137. final int firstChar;
  138. // Determine the first char we care about
  139. if (selectionStartLine < line || selectionStartChar < chars) {
  140. firstChar = 0;
  141. } else {
  142. firstChar = selectionStartChar - chars;
  143. }
  144. // ... And the last
  145. final int lastChar;
  146. if (selectionEndLine > line || selectionEndChar > chars + layout.getCharacterCount()) {
  147. lastChar = layout.getCharacterCount();
  148. } else {
  149. lastChar = selectionEndChar - chars;
  150. }
  151. // If the selection includes the chars we're showing
  152. if (lastChar > 0 && firstChar < layout.getCharacterCount() && lastChar > firstChar) {
  153. doHighlight(line,
  154. layout.getLogicalHighlightShape(firstChar, lastChar), g, canvasWidth,
  155. drawPosY, drawPosX, chars + firstChar, chars + lastChar,
  156. lastChar == layout.getCharacterCount());
  157. }
  158. }
  159. }
  160. private void doHighlight(final int line, final Shape logicalHighlightShape, final Graphics2D g,
  161. final float canvasWidth, final float drawPosY, final float drawPosX,
  162. final int firstChar, final int lastChar, final boolean isEndOfLine) {
  163. final AttributedCharacterIterator iterator = document.getStyledLine(line).getIterator();
  164. final AttributedString as = new AttributedString(iterator, firstChar, lastChar);
  165. as.addAttribute(TextAttribute.FOREGROUND, highlightForeground);
  166. as.addAttribute(TextAttribute.BACKGROUND, highlightBackground);
  167. final TextLayout newLayout = new TextLayout(as.getIterator(), g.getFontRenderContext());
  168. final Rectangle2D bounds = logicalHighlightShape.getBounds();
  169. g.setColor(highlightBackground);
  170. g.translate(drawPosX + bounds.getX(), drawPosY);
  171. if (isEndOfLine) {
  172. g.fill(new Rectangle2D.Double(
  173. bounds.getWidth(),
  174. bounds.getY(),
  175. canvasWidth - bounds.getX() - bounds.getWidth(),
  176. bounds.getHeight()));
  177. }
  178. newLayout.draw(g, 0, 0);
  179. g.translate(-drawPosX - bounds.getX(), -drawPosY);
  180. }
  181. }