package org.expeditee.gio.swing; import java.awt.Rectangle; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.text.AttributedString; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import org.expeditee.core.Font; import org.expeditee.core.Line; import org.expeditee.core.Point; import org.expeditee.core.TextHitInfo; import org.expeditee.core.TextLayout; import org.expeditee.core.bounds.AxisAlignedBoxBounds; import org.expeditee.gio.TextLayoutManager; /** * TODO: Comment. cts16 * * @author cts16 */ public class SwingTextLayoutManager extends TextLayoutManager { /** Singleton instance. */ private static SwingTextLayoutManager _instance = null; /** Singleton instantiator. */ public static SwingTextLayoutManager getInstance() { if (_instance == null) { _instance = new SwingTextLayoutManager(); } return _instance; } /** Mapping from Expeditee text-layout objects to swing equivalents. */ private HashMap _layoutMap; private SwingTextLayoutManager() { _layoutMap = new HashMap(); } private synchronized void register(TextLayout layout, java.awt.font.TextLayout swingLayout) { if (layout == null || swingLayout == null) { return; } _layoutMap.put(layout.getHandle(), swingLayout); } public synchronized java.awt.font.TextLayout getInternalLayout(TextLayout layout) { if (!isTextLayoutValid(layout)) { return null; } return _layoutMap.get(layout.getHandle()); } public synchronized boolean isTextLayoutValid(TextLayout layout) { if (layout == null) { return false; } return _layoutMap.containsKey(layout.getHandle()); } @Override public float getAdvance(TextLayout layout) { if (!isTextLayoutValid(layout)) { return Float.NaN; } return getInternalLayout(layout).getAdvance(); } @Override public int getCharacterCount(TextLayout layout) { if (!isTextLayoutValid(layout)) { return 0; } return getInternalLayout(layout).getCharacterCount(); } @Override public TextHitInfo getNextLeftHit(TextLayout layout, int offset) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingTextHitInfo(getInternalLayout(layout).getNextLeftHit(offset)); } @Override public TextHitInfo getNextLeftHit(TextLayout layout, TextHitInfo hit) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingTextHitInfo(getInternalLayout(layout).getNextLeftHit(SwingConversions.toSwingTextHitInfo(hit))); } @Override public TextHitInfo getNextRightHit(TextLayout layout, int offset) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingTextHitInfo(getInternalLayout(layout).getNextRightHit(offset)); } @Override public TextHitInfo getNextRightHit(TextLayout layout, TextHitInfo hit) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingTextHitInfo(getInternalLayout(layout).getNextRightHit(SwingConversions.toSwingTextHitInfo(hit))); } @Override public float[] getCaretInfo(TextLayout layout, TextHitInfo hit) { if (!isTextLayoutValid(layout)) { return null; } return getInternalLayout(layout).getCaretInfo(SwingConversions.toSwingTextHitInfo(hit)); } @Override public TextHitInfo hitTestChar(TextLayout layout, float x, float y) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingTextHitInfo(getInternalLayout(layout).hitTestChar(x, y)); } @Override public AxisAlignedBoxBounds getPixelBounds(TextLayout layout, float x, float y) { if (!isTextLayoutValid(layout)) { return null; } return SwingConversions.fromSwingRectangle(getInternalLayout(layout).getPixelBounds(null, x, y)); } @Override public AxisAlignedBoxBounds getLogicalHighlightShape(TextLayout layout, int firstEndpoint, int secondEndpoint) { if (!isTextLayoutValid(layout)) { return null; } Rectangle rect = getInternalLayout(layout).getLogicalHighlightShape(firstEndpoint, secondEndpoint).getBounds(); return SwingConversions.fromSwingRectangle(rect); } @Override public float getAscent(TextLayout layout) { if (!isTextLayoutValid(layout)) { return Float.NaN; } return getInternalLayout(layout).getAscent(); } @Override public float getDescent(TextLayout layout) { if (!isTextLayoutValid(layout)) { return Float.NaN; } return getInternalLayout(layout).getDescent(); } @Override public float getLeading(TextLayout layout) { if (!isTextLayoutValid(layout)) { return Float.NaN; } return getInternalLayout(layout).getLeading(); } @Override public synchronized void releaseLayout(TextLayout layout) { if (!isTextLayoutValid(layout)) { return; } _layoutMap.remove(layout.getHandle()); } @Override public List layoutString(String string, Font font, Point start, Line[] lineBreakers, int widthLimit, int lineSpacing, boolean dontBreakWords, boolean fullJustify) { if (string == null || font == null || start == null) { return null; } // Make sure we have a swing font to use java.awt.Font swingFont; SwingFontManager fontManager = SwingMiscManager.getIfUsingSwingFontManager(); if (fontManager == null) { return null; } swingFont = fontManager.getInternalFont(font); if (swingFont == null) { return null; } // Temporary list to accumulate the TextLayouts in to List layouts = new LinkedList(); List offsets = new LinkedList(); offsets.add(0); List positions = new LinkedList(); positions.add(new Point(start)); // Set up a line-break measurer to layout the text AttributedString paragraphText = new AttributedString(string); paragraphText.addAttribute(TextAttribute.FONT, swingFont); FontRenderContext frc = new FontRenderContext(null, true, true); LineBreakMeasurer lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(), frc); lineBreaker.setPosition(0); // Keep laying out text until the input string is completely laid out while (lineBreaker.getPosition() < string.length()) { int width = widthLimit; if (lineBreakers != null) { width = Math.min(width, (int) getLineWidth(start, lineBreakers)); } int end = string.length(); for (int i = lineBreaker.getPosition() + 1; i < string.length(); i++) { if (string.charAt(i) == '\n') { end = i; break; } } java.awt.font.TextLayout layout = null; try { layout = lineBreaker.nextLayout(width, end, dontBreakWords); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } // If it's impossible to layout any more text without breaking a word, just do it if (layout == null && width == widthLimit) { layout = lineBreaker.nextLayout(width, end, false); // If still impossible, give up if (layout == null) { break; } } if (fullJustify && lineBreaker.getPosition() < string.length()) { layout = layout.getJustifiedLayout(width); } layouts.add(layout); offsets.add(lineBreaker.getPosition()); int lineDrop = (int) (layout.getAscent() + layout.getDescent()); if (lineSpacing >= 0) { lineDrop += lineSpacing; } else { lineDrop += layout.getLeading(); } start.setY(start.getY() + lineDrop); positions.add(new Point(start)); } // Convert the accumulated list into an internal-type array List ret = new LinkedList(); for (int i = 0; i < layouts.size(); i++) { java.awt.font.TextLayout swingLayout = layouts.get(i); int startCharIndex = offsets.get(i); int endCharIndex = offsets.get(i + 1) - 1; String line = string.substring(startCharIndex, endCharIndex + 1); TextLayout layout = TextLayout.get(line, font, startCharIndex, endCharIndex); register(layout, swingLayout); ret.add(layout); } return ret; } @Override public int getStringWidth(Font font, String string) { SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager(); SwingFontManager f = SwingMiscManager.getIfUsingSwingFontManager(); if (g == null || f == null) { return -1; } if (font != null) { return g.getFontMetrics(f.getInternalFont(font)).stringWidth(string); } else { return g.getFontMetrics(f.getInternalFont(f.getDefaultFont())).stringWidth(string); } } }