OSDN Git Service

Merge commit '2458eff3aea04f67893bc824b5cf896fbb767332'
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / GlyphDraw.java
diff --git a/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java b/src/main/java/jp/sourceforge/jindolf/GlyphDraw.java
new file mode 100644 (file)
index 0000000..f55c78f
--- /dev/null
@@ -0,0 +1,765 @@
+/*\r
+ * Text-Glyph Drawing\r
+ *\r
+ * Copyright(c) 2008 olyutorskii\r
+ * $Id: GlyphDraw.java 959 2009-12-14 14:11:01Z olyutorskii $\r
+ */\r
+\r
+package jp.sourceforge.jindolf;\r
+\r
+import java.awt.Color;\r
+import java.awt.FontMetrics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Point;\r
+import java.awt.Rectangle;\r
+import java.awt.Shape;\r
+import java.awt.font.GlyphVector;\r
+import java.awt.geom.Rectangle2D;\r
+import java.io.IOException;\r
+import java.text.CharacterIterator;\r
+import java.util.Collection;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+import javax.swing.SwingConstants;\r
+\r
+/**\r
+ * 複数行の文字列を矩形内に描画する。\r
+ * 制御文字は'\n'のみサポート。\r
+ */\r
+public class GlyphDraw extends AbstractTextRow implements SwingConstants{\r
+\r
+    private static final Color COLOR_SELECTION = new Color(0xb8cfe5);\r
+    private static final Color COLOR_SEARCHHIT = new Color(0xb2b300);\r
+    private static final Color COLOR_HOTTARGET = Color.ORANGE;\r
+\r
+    private Color foregroundColor = Color.WHITE;\r
+    private final CharSequence source;\r
+\r
+    private float[] dimArray;\r
+    private final List<GlyphVector> lines = new LinkedList<GlyphVector>();\r
+    private Collection<Anchor> anchorSet;\r
+    private final List<MatchInfo> matchList = new LinkedList<MatchInfo>();\r
+    private MatchInfo hotTarget = null;\r
+\r
+    private int selectStart = -1;\r
+    private int selectLast  = -1;\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param source 文字列\r
+     */\r
+    public GlyphDraw(CharSequence source){\r
+        this(source, FontInfo.DEFAULT_FONTINFO);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * コンストラクタ。\r
+     * @param source 文字列\r
+     * @param fontInfo フォント設定\r
+     */\r
+    public GlyphDraw(CharSequence source, FontInfo fontInfo){\r
+        super(fontInfo);\r
+\r
+        this.source = source;\r
+\r
+        GlyphVector gv = createGlyphVector(this.source);\r
+\r
+        int sourceLength = gv.getNumGlyphs();\r
+\r
+        this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null);\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 前景色を得る。\r
+     * @return 前景色\r
+     */\r
+    public Color getColor(){\r
+        return this.foregroundColor;\r
+    }\r
+\r
+    /**\r
+     * 前景色を設定する。\r
+     * @param color 前景色\r
+     */\r
+    public void setColor(Color color){\r
+        this.foregroundColor = color;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * アンカーを設定する。\r
+     * アンカーの位置指定はコンストラクタに与えた文字列に対するものでなければ\r
+     * ならない。\r
+     * @param anchorSet アンカーの集合\r
+     */\r
+    public void setAnchorSet(Collection<Anchor> anchorSet){\r
+        this.anchorSet = anchorSet;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 文字列の占めるピクセル幅を返す。\r
+     * @param fromPos 文字列開始位置\r
+     * @param toPos 文字列終了位置\r
+     * @return ピクセル幅\r
+     */\r
+    public float getSpan(int fromPos, int toPos){\r
+        float from = this.dimArray[fromPos   * 2];\r
+        float to   = this.dimArray[(toPos+1) * 2];\r
+        float span = to - from;\r
+        return span;\r
+    }\r
+\r
+    /**\r
+     * 指定領域の文字列から行情報を生成し内部に登録する。\r
+     * @param from 文字列開始位置\r
+     * @param to 文字列終了位置\r
+     * @return 行情報\r
+     */\r
+    protected GlyphVector createLine(int from, int to){\r
+        GlyphVector line = createGlyphVector(this.source, from, to + 1);\r
+        this.lines.add(line);\r
+        return line;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     */\r
+    // TODO 最後が \n で終わるダイアログが無限再帰を起こす?\r
+    public Rectangle recalcBounds(){\r
+        float newWidth = (float) getWidth();\r
+        this.lines.clear();\r
+        CharacterIterator iterator;\r
+        iterator = new SequenceCharacterIterator(this.source);\r
+        int from = iterator.getIndex();\r
+        int to = from;\r
+        for(;;){\r
+           char ch =  iterator.current();\r
+\r
+           if(ch == CharacterIterator.DONE){\r
+               if(from < to){\r
+                   createLine(from, to - 1);\r
+               }\r
+               break;\r
+           }\r
+\r
+           if(ch == '\n'){\r
+               createLine(from, to);\r
+               to++;\r
+               from = to;\r
+               iterator.next();\r
+               continue;\r
+           }\r
+\r
+           float fwidth = getSpan(from, to);\r
+           if(fwidth > newWidth){\r
+               if(from < to){\r
+                   createLine(from, to - 1);\r
+                   from = to;\r
+               }else{\r
+                   createLine(from, to);\r
+                   to++;\r
+                   from = to;\r
+                   iterator.next();\r
+               }\r
+               continue;\r
+           }\r
+\r
+           to++;\r
+           iterator.next();\r
+        }\r
+\r
+        int totalWidth = 0;\r
+        int totalHeight = 0;\r
+        for(GlyphVector gv : this.lines){\r
+            Rectangle2D r2d = gv.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+            totalWidth = Math.max(totalWidth, rect.width);\r
+            totalHeight += rect.height;\r
+        }\r
+\r
+        this.bounds.width  = totalWidth;\r
+        this.bounds.height = totalHeight;\r
+\r
+        return this.bounds;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param fontInfo {@inheritDoc}\r
+     */\r
+    @Override\r
+    public void setFontInfo(FontInfo fontInfo){\r
+        super.setFontInfo(fontInfo);\r
+\r
+        GlyphVector gv = createGlyphVector(this.source);\r
+\r
+        int sourceLength = gv.getNumGlyphs();\r
+\r
+        this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null);\r
+\r
+        recalcBounds();\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 指定された点座標が文字列のどこを示すか判定する。\r
+     * @param pt 点座標\r
+     * @return 文字位置。座標が文字列以外を示す場合は-1を返す。\r
+     */\r
+    public int getCharIndex(Point pt){\r
+        if( ! this.bounds.contains(pt) ) return -1;\r
+\r
+        int sPos = 0;\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y;\r
+        for(GlyphVector gv : this.lines){\r
+            Rectangle2D r2d = gv.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+            rect.x = xPos;\r
+            rect.y = yPos;\r
+            int sourceLength = gv.getNumGlyphs();\r
+            if(rect.contains(pt)){\r
+                for(int pos = 0; pos < sourceLength; pos++){\r
+                    float span = getSpan(sPos, sPos+pos);\r
+                    if(span+xPos > pt.x) return sPos + pos;\r
+                }\r
+                return -1;\r
+            }\r
+            yPos += rect.height;\r
+            sPos += sourceLength;\r
+        }\r
+\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param appendable {@inheritDoc}\r
+     * @return {@inheritDoc}\r
+     * @throws java.io.IOException {@inheritDoc}\r
+     */\r
+    public Appendable appendSelected(Appendable appendable)\r
+            throws IOException{\r
+        if(this.selectStart < 0 || this.selectLast < 0) return appendable;\r
+        CharSequence subsel;\r
+        subsel = this.source.subSequence(this.selectStart,\r
+                                         this.selectLast + 1);\r
+        appendable.append(subsel);\r
+        return appendable;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     */\r
+    public void clearSelect(){\r
+        this.selectStart = -1;\r
+        this.selectLast  = -1;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 指定した部分文字列を選択された状態にする。\r
+     * @param start 文字列開始位置\r
+     * @param last 文字列終了位置\r
+     */\r
+    public void select(int start, int last){\r
+        if(start < last){\r
+            this.selectStart = start;\r
+            this.selectLast  = last;\r
+        }else{\r
+            this.selectStart = last;\r
+            this.selectLast  = start;\r
+        }\r
+        this.selectLast  = Math.min(this.source.length() - 1,\r
+                                    this.selectLast );\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param from {@inheritDoc}\r
+     * @param to {@inheritDoc}\r
+     */\r
+    public void drag(Point from, Point to){\r
+        Point fromPt = from;\r
+        Point toPt = to;\r
+        if(fromPt.y > toPt.y || (fromPt.y == toPt.y && fromPt.x > toPt.x)){\r
+            Point swapPt = fromPt;\r
+            fromPt = toPt;\r
+            toPt = swapPt;\r
+        }\r
+\r
+        int fromDirection = GUIUtils.getDirection(this.bounds, fromPt);\r
+        int toDirection   = GUIUtils.getDirection(this.bounds, toPt);\r
+\r
+        if(fromDirection == toDirection){\r
+            if(   fromDirection == NORTH\r
+               || fromDirection == SOUTH){\r
+                clearSelect();\r
+                return;\r
+            }\r
+        }\r
+\r
+        int fromIndex = -1;\r
+        int toIndex   = -1;\r
+\r
+        if(fromDirection == NORTH){\r
+            fromIndex = 0;\r
+        }\r
+        if(toDirection == SOUTH){\r
+            toIndex = this.source.length() - 1;\r
+        }\r
+\r
+        if(fromIndex < 0){\r
+            fromIndex = getCharIndex(fromPt);\r
+        }\r
+        if(toIndex < 0){\r
+            toIndex = getCharIndex(toPt);\r
+        }\r
+\r
+        if(fromIndex >= 0 && toIndex >= 0){\r
+            select(fromIndex, toIndex);\r
+            return;\r
+        }\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y;\r
+        int accumPos = 0;\r
+        for(GlyphVector gv : this.lines){\r
+            int glyphStart = accumPos;\r
+            int glyphLast   = accumPos + gv.getNumGlyphs() - 1;\r
+            Rectangle2D r2d = gv.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+            rect.x += xPos;\r
+            rect.y = yPos;\r
+\r
+            if(   fromIndex < 0\r
+               && GUIUtils.getDirection(rect, fromPt) == SOUTH){\r
+                yPos += rect.height;\r
+                accumPos = glyphLast + 1;\r
+                continue;\r
+            }else if(   toIndex < 0\r
+                     && GUIUtils.getDirection(rect, toPt) == NORTH){\r
+                break;\r
+            }\r
+\r
+            if(fromIndex < 0){\r
+                int dir = GUIUtils.getDirection(rect, fromPt);\r
+                if(dir == EAST){\r
+                    fromIndex = glyphStart;\r
+                }else if(dir == WEST){\r
+                    fromIndex = glyphLast+1;\r
+                }\r
+            }\r
+            if(toIndex < 0){\r
+                int dir = GUIUtils.getDirection(rect, toPt);\r
+                if(dir == EAST){\r
+                    toIndex = glyphStart - 1;\r
+                }else if(dir == WEST){\r
+                    toIndex = glyphLast;\r
+                }\r
+            }\r
+\r
+            if(fromIndex >= 0 && toIndex >= 0){\r
+                select(fromIndex, toIndex);\r
+                return;\r
+            }\r
+\r
+            yPos += rect.height;\r
+            accumPos = glyphLast + 1;\r
+        }\r
+\r
+        clearSelect();\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 文字列検索がヒットした箇所のハイライト描画を行う。\r
+     * @param g グラフィックスコンテキスト\r
+     */\r
+    private void paintRegexHitted(Graphics2D g){\r
+        if(this.matchList.size() <= 0) return;\r
+\r
+        FontMetrics metrics = g.getFontMetrics();\r
+        final int ascent  = metrics.getAscent();\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y + ascent;\r
+\r
+        int accumPos = 0;\r
+\r
+        for(GlyphVector line : this.lines){\r
+            int glyphStart = accumPos;\r
+            int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
+\r
+            for(MatchInfo match : this.matchList){\r
+                int matchStart = match.getStartPos();\r
+                int matchLast  = match.getEndPos() - 1;\r
+\r
+                if(matchLast < glyphStart) continue;\r
+                if(glyphLast < matchStart) break;\r
+\r
+                int hilightStart = Math.max(matchStart, glyphStart);\r
+                int hilightLast  = Math.min(matchLast,  glyphLast);\r
+                Shape shape;\r
+                shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
+                Rectangle hilight = shape.getBounds();\r
+                shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
+                hilight.add(shape.getBounds());\r
+\r
+                if(match == this.hotTarget){\r
+                    g.setColor(COLOR_HOTTARGET);\r
+                }else{\r
+                    g.setColor(COLOR_SEARCHHIT);\r
+                }\r
+\r
+                g.fillRect(xPos + hilight.x,\r
+                           yPos + hilight.y,\r
+                           hilight.width,\r
+                           hilight.height );\r
+            }\r
+\r
+            Rectangle2D r2d = line.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+\r
+            yPos += rect.height;\r
+\r
+            accumPos = glyphLast + 1;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 選択文字列のハイライト描画を行う。\r
+     * @param g グラフィックスコンテキスト\r
+     */\r
+    private void paintSelected(Graphics2D g){\r
+        if(this.selectStart < 0 || this.selectLast < 0) return;\r
+\r
+        g.setColor(COLOR_SELECTION);\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y;\r
+\r
+        int accumPos = 0;\r
+\r
+        for(GlyphVector line : this.lines){\r
+            int glyphStart = accumPos;\r
+            int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
+\r
+            if(this.selectLast < glyphStart) break;\r
+\r
+            Rectangle2D r2d = line.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+\r
+            if(glyphLast < this.selectStart){\r
+                yPos += rect.height;\r
+                accumPos = glyphLast + 1;\r
+                continue;\r
+            }\r
+\r
+            int hilightStart = Math.max(this.selectStart, glyphStart);\r
+            int hilightLast  = Math.min(this.selectLast,  glyphLast);\r
+            Shape shape;\r
+            shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
+            Rectangle hilight = shape.getBounds();\r
+            shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
+            hilight.add(shape.getBounds());\r
+\r
+            g.fillRect(xPos + hilight.x,\r
+                       yPos,\r
+                       hilight.width,\r
+                       hilight.height );\r
+\r
+            yPos += rect.height;\r
+            accumPos = glyphLast + 1;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * アンカー文字列のハイライト描画を行う。\r
+     * @param g グラフィックスコンテキスト\r
+     */\r
+    private void paintAnchorBack(Graphics2D g){\r
+        if(this.anchorSet == null) return;\r
+        if(this.anchorSet.size() <= 0) return;\r
+\r
+        FontMetrics metrics = g.getFontMetrics();\r
+        final int ascent  = metrics.getAscent();\r
+\r
+        g.setColor(Color.GRAY);\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y + ascent;\r
+\r
+        int accumPos = 0;\r
+\r
+        for(GlyphVector line : this.lines){\r
+            int glyphStart = accumPos;\r
+            int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
+\r
+            for(Anchor anchor : this.anchorSet){\r
+                int anchorStart = anchor.getStartPos();\r
+                int anchorLast   = anchor.getEndPos() - 1;\r
+\r
+                if(anchorLast < glyphStart) continue;\r
+                if(glyphLast < anchorStart) break;\r
+\r
+                int hilightStart = Math.max(anchorStart, glyphStart);\r
+                int hilightLast  = Math.min(anchorLast,  glyphLast);\r
+                Shape shape;\r
+                shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
+                Rectangle hilight = shape.getBounds();\r
+                shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
+                hilight.add(shape.getBounds());\r
+\r
+                g.fillRect(xPos + hilight.x,\r
+                           yPos + hilight.y,\r
+                           hilight.width,\r
+                           hilight.height );\r
+            }\r
+\r
+            Rectangle2D r2d = line.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+\r
+            yPos += rect.height;\r
+\r
+            accumPos = glyphLast + 1;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * {@inheritDoc}\r
+     * @param g {@inheritDoc}\r
+     */\r
+    public void paint(Graphics2D g){\r
+        g.setFont(this.fontInfo.getFont());\r
+        FontMetrics metrics = g.getFontMetrics();\r
+        int ascent  = metrics.getAscent();\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y + ascent;\r
+\r
+        paintAnchorBack(g);\r
+        paintRegexHitted(g);\r
+        paintSelected(g);\r
+\r
+        g.setColor(this.foregroundColor);\r
+        for(GlyphVector gv : this.lines){\r
+            g.drawGlyphVector(gv, xPos, yPos);\r
+\r
+            Rectangle2D r2d = gv.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+\r
+            yPos += rect.height;\r
+        }\r
+\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 与えられた座標にアンカー文字列が存在すればAnchorを返す。\r
+     * @param pt 座標\r
+     * @return アンカー\r
+     */\r
+    public Anchor getAnchor(Point pt){\r
+        int targetIdx = getCharIndex(pt);\r
+        if(targetIdx < 0) return null;\r
+\r
+        for(Anchor anchor : this.anchorSet){\r
+            int anchorStart = anchor.getStartPos();\r
+            int anchorEnd   = anchor.getEndPos();\r
+            if(anchorStart <= targetIdx && targetIdx <= anchorEnd - 1){\r
+                return anchor;\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。\r
+     * @param pt 座標\r
+     * @return 検索マッチインデックス\r
+     */\r
+    public int getRegexMatchIndex(Point pt){\r
+        int targetIdx = getCharIndex(pt);\r
+        if(targetIdx < 0) return -1;\r
+\r
+        int index = 0;\r
+        for(MatchInfo info : this.matchList){\r
+            int matchStart = info.getStartPos();\r
+            int matchEnd   = info.getEndPos();\r
+            if(matchStart <= targetIdx && targetIdx <= matchEnd - 1){\r
+                return index;\r
+            }\r
+            index++;\r
+        }\r
+\r
+        return -1;\r
+    }\r
+\r
+    /**\r
+     * 検索文字列パターンを設定する。\r
+     * @param searchRegex パターン\r
+     * @return ヒット数\r
+     */\r
+    public int setRegex(Pattern searchRegex){\r
+        clearHotTarget();\r
+        this.matchList.clear();\r
+        if(searchRegex == null) return 0;\r
+\r
+        Matcher matcher = searchRegex.matcher(this.source);\r
+        while(matcher.find()){\r
+            int startPos = matcher.start();\r
+            int endPos   = matcher.end();\r
+            if(startPos >= endPos) break;  // 長さ0マッチは無視\r
+            MatchInfo matchInfo = new MatchInfo(startPos, endPos);\r
+            this.matchList.add(matchInfo);\r
+        }\r
+\r
+        return getRegexMatches();\r
+    }\r
+\r
+    /**\r
+     * 検索ハイライトインデックスを返す。\r
+     * @return 検索ハイライトインデックス。見つからなければ-1。\r
+     */\r
+    public int getHotTargetIndex(){\r
+        return this.matchList.indexOf(this.hotTarget);\r
+    }\r
+\r
+    /**\r
+     * 検索ハイライトを設定する。\r
+     * @param index ハイライトインデックス。負ならハイライト全クリア。\r
+     */\r
+    public void setHotTargetIndex(int index){\r
+        if(index < 0){\r
+            clearHotTarget();\r
+            return;\r
+        }\r
+        this.hotTarget = this.matchList.get(index);\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 検索一致件数を返す。\r
+     * @return 検索一致件数\r
+     */\r
+    public int getRegexMatches(){\r
+        return this.matchList.size();\r
+    }\r
+\r
+    /**\r
+     * 特別な検索ハイライト描画をクリアする。\r
+     */\r
+    public void clearHotTarget(){\r
+        this.hotTarget = null;\r
+        return;\r
+    }\r
+\r
+    /**\r
+     * 特別な検索ハイライト領域の寸法を返す。\r
+     * @return ハイライト領域寸法\r
+     */\r
+    public Rectangle getHotTargetRectangle(){\r
+        Rectangle result = null;\r
+\r
+        if(this.hotTarget == null) return result;\r
+\r
+        int xPos = this.bounds.x;\r
+        int yPos = this.bounds.y;\r
+\r
+        int accumPos = 0;\r
+\r
+        int matchStart = this.hotTarget.getStartPos();\r
+        int matchLast  = this.hotTarget.getEndPos() - 1;\r
+\r
+        for(GlyphVector gv : this.lines){\r
+            int glyphStart = accumPos;\r
+            int glyphLast   = accumPos + gv.getNumGlyphs() - 1;\r
+\r
+            if(matchLast < glyphStart) break;\r
+\r
+            if(matchStart <= glyphLast){\r
+                int hilightStart = Math.max(matchStart, glyphStart);\r
+                int hilightLast  = Math.min(matchLast,  glyphLast);\r
+\r
+                Shape shape;\r
+                shape = gv.getGlyphLogicalBounds(hilightStart - glyphStart);\r
+                Rectangle hilight = shape.getBounds();\r
+                shape = gv.getGlyphLogicalBounds(hilightLast - glyphStart);\r
+                hilight.add(shape.getBounds());\r
+\r
+                Rectangle temp = new Rectangle(xPos + hilight.x,\r
+                                               yPos,\r
+                                               hilight.width,\r
+                                               hilight.height);\r
+                if(result == null){\r
+                    result = temp;\r
+                }else{\r
+                    result.add(temp);\r
+                }\r
+            }\r
+\r
+            Rectangle2D r2d = gv.getLogicalBounds();\r
+            Rectangle rect = r2d.getBounds();\r
+            yPos += rect.height;\r
+\r
+            accumPos = glyphLast + 1;\r
+        }\r
+\r
+        return result;\r
+    }\r
+\r
+    /**\r
+     * 検索ヒット情報。\r
+     */\r
+    private static class MatchInfo{\r
+\r
+        private final int startPos;\r
+        private final int endPos;\r
+\r
+        /**\r
+         * コンストラクタ。\r
+         * @param startPos ヒット開始位置\r
+         * @param endPos ヒット終了位置\r
+         */\r
+        public MatchInfo(int startPos, int endPos){\r
+            super();\r
+            this.startPos = startPos;\r
+            this.endPos   = endPos;\r
+            return;\r
+        }\r
+\r
+        /**\r
+         * ヒット開始位置を取得する。\r
+         * @return ヒット開始位置\r
+         */\r
+        public int getStartPos(){\r
+            return this.startPos;\r
+        }\r
+\r
+        /**\r
+         * ヒット終了位置を取得する。\r
+         * @return ヒット終了位置\r
+         */\r
+        public int getEndPos(){\r
+            return this.endPos;\r
+        }\r
+    }\r
+\r
+}\r