OSDN Git Service

f55c78f5997a1bc4f5d483dc34dfeb7cb1f6206a
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / GlyphDraw.java
1 /*\r
2  * Text-Glyph Drawing\r
3  *\r
4  * Copyright(c) 2008 olyutorskii\r
5  * $Id: GlyphDraw.java 959 2009-12-14 14:11:01Z olyutorskii $\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.awt.Color;\r
11 import java.awt.FontMetrics;\r
12 import java.awt.Graphics2D;\r
13 import java.awt.Point;\r
14 import java.awt.Rectangle;\r
15 import java.awt.Shape;\r
16 import java.awt.font.GlyphVector;\r
17 import java.awt.geom.Rectangle2D;\r
18 import java.io.IOException;\r
19 import java.text.CharacterIterator;\r
20 import java.util.Collection;\r
21 import java.util.LinkedList;\r
22 import java.util.List;\r
23 import java.util.regex.Matcher;\r
24 import java.util.regex.Pattern;\r
25 import javax.swing.SwingConstants;\r
26 \r
27 /**\r
28  * 複数行の文字列を矩形内に描画する。\r
29  * 制御文字は'\n'のみサポート。\r
30  */\r
31 public class GlyphDraw extends AbstractTextRow implements SwingConstants{\r
32 \r
33     private static final Color COLOR_SELECTION = new Color(0xb8cfe5);\r
34     private static final Color COLOR_SEARCHHIT = new Color(0xb2b300);\r
35     private static final Color COLOR_HOTTARGET = Color.ORANGE;\r
36 \r
37     private Color foregroundColor = Color.WHITE;\r
38     private final CharSequence source;\r
39 \r
40     private float[] dimArray;\r
41     private final List<GlyphVector> lines = new LinkedList<GlyphVector>();\r
42     private Collection<Anchor> anchorSet;\r
43     private final List<MatchInfo> matchList = new LinkedList<MatchInfo>();\r
44     private MatchInfo hotTarget = null;\r
45 \r
46     private int selectStart = -1;\r
47     private int selectLast  = -1;\r
48 \r
49     /**\r
50      * コンストラクタ。\r
51      * @param source 文字列\r
52      */\r
53     public GlyphDraw(CharSequence source){\r
54         this(source, FontInfo.DEFAULT_FONTINFO);\r
55         return;\r
56     }\r
57 \r
58     /**\r
59      * コンストラクタ。\r
60      * @param source 文字列\r
61      * @param fontInfo フォント設定\r
62      */\r
63     public GlyphDraw(CharSequence source, FontInfo fontInfo){\r
64         super(fontInfo);\r
65 \r
66         this.source = source;\r
67 \r
68         GlyphVector gv = createGlyphVector(this.source);\r
69 \r
70         int sourceLength = gv.getNumGlyphs();\r
71 \r
72         this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null);\r
73 \r
74         return;\r
75     }\r
76 \r
77     /**\r
78      * 前景色を得る。\r
79      * @return 前景色\r
80      */\r
81     public Color getColor(){\r
82         return this.foregroundColor;\r
83     }\r
84 \r
85     /**\r
86      * 前景色を設定する。\r
87      * @param color 前景色\r
88      */\r
89     public void setColor(Color color){\r
90         this.foregroundColor = color;\r
91         return;\r
92     }\r
93 \r
94     /**\r
95      * アンカーを設定する。\r
96      * アンカーの位置指定はコンストラクタに与えた文字列に対するものでなければ\r
97      * ならない。\r
98      * @param anchorSet アンカーの集合\r
99      */\r
100     public void setAnchorSet(Collection<Anchor> anchorSet){\r
101         this.anchorSet = anchorSet;\r
102         return;\r
103     }\r
104 \r
105     /**\r
106      * 文字列の占めるピクセル幅を返す。\r
107      * @param fromPos 文字列開始位置\r
108      * @param toPos 文字列終了位置\r
109      * @return ピクセル幅\r
110      */\r
111     public float getSpan(int fromPos, int toPos){\r
112         float from = this.dimArray[fromPos   * 2];\r
113         float to   = this.dimArray[(toPos+1) * 2];\r
114         float span = to - from;\r
115         return span;\r
116     }\r
117 \r
118     /**\r
119      * 指定領域の文字列から行情報を生成し内部に登録する。\r
120      * @param from 文字列開始位置\r
121      * @param to 文字列終了位置\r
122      * @return 行情報\r
123      */\r
124     protected GlyphVector createLine(int from, int to){\r
125         GlyphVector line = createGlyphVector(this.source, from, to + 1);\r
126         this.lines.add(line);\r
127         return line;\r
128     }\r
129 \r
130     /**\r
131      * {@inheritDoc}\r
132      * @return {@inheritDoc}\r
133      */\r
134     // TODO 最後が \n で終わるダイアログが無限再帰を起こす?\r
135     public Rectangle recalcBounds(){\r
136         float newWidth = (float) getWidth();\r
137         this.lines.clear();\r
138         CharacterIterator iterator;\r
139         iterator = new SequenceCharacterIterator(this.source);\r
140         int from = iterator.getIndex();\r
141         int to = from;\r
142         for(;;){\r
143            char ch =  iterator.current();\r
144 \r
145            if(ch == CharacterIterator.DONE){\r
146                if(from < to){\r
147                    createLine(from, to - 1);\r
148                }\r
149                break;\r
150            }\r
151 \r
152            if(ch == '\n'){\r
153                createLine(from, to);\r
154                to++;\r
155                from = to;\r
156                iterator.next();\r
157                continue;\r
158            }\r
159 \r
160            float fwidth = getSpan(from, to);\r
161            if(fwidth > newWidth){\r
162                if(from < to){\r
163                    createLine(from, to - 1);\r
164                    from = to;\r
165                }else{\r
166                    createLine(from, to);\r
167                    to++;\r
168                    from = to;\r
169                    iterator.next();\r
170                }\r
171                continue;\r
172            }\r
173 \r
174            to++;\r
175            iterator.next();\r
176         }\r
177 \r
178         int totalWidth = 0;\r
179         int totalHeight = 0;\r
180         for(GlyphVector gv : this.lines){\r
181             Rectangle2D r2d = gv.getLogicalBounds();\r
182             Rectangle rect = r2d.getBounds();\r
183             totalWidth = Math.max(totalWidth, rect.width);\r
184             totalHeight += rect.height;\r
185         }\r
186 \r
187         this.bounds.width  = totalWidth;\r
188         this.bounds.height = totalHeight;\r
189 \r
190         return this.bounds;\r
191     }\r
192 \r
193     /**\r
194      * {@inheritDoc}\r
195      * @param fontInfo {@inheritDoc}\r
196      */\r
197     @Override\r
198     public void setFontInfo(FontInfo fontInfo){\r
199         super.setFontInfo(fontInfo);\r
200 \r
201         GlyphVector gv = createGlyphVector(this.source);\r
202 \r
203         int sourceLength = gv.getNumGlyphs();\r
204 \r
205         this.dimArray = gv.getGlyphPositions(0, sourceLength+1, null);\r
206 \r
207         recalcBounds();\r
208 \r
209         return;\r
210     }\r
211 \r
212     /**\r
213      * 指定された点座標が文字列のどこを示すか判定する。\r
214      * @param pt 点座標\r
215      * @return 文字位置。座標が文字列以外を示す場合は-1を返す。\r
216      */\r
217     public int getCharIndex(Point pt){\r
218         if( ! this.bounds.contains(pt) ) return -1;\r
219 \r
220         int sPos = 0;\r
221         int xPos = this.bounds.x;\r
222         int yPos = this.bounds.y;\r
223         for(GlyphVector gv : this.lines){\r
224             Rectangle2D r2d = gv.getLogicalBounds();\r
225             Rectangle rect = r2d.getBounds();\r
226             rect.x = xPos;\r
227             rect.y = yPos;\r
228             int sourceLength = gv.getNumGlyphs();\r
229             if(rect.contains(pt)){\r
230                 for(int pos = 0; pos < sourceLength; pos++){\r
231                     float span = getSpan(sPos, sPos+pos);\r
232                     if(span+xPos > pt.x) return sPos + pos;\r
233                 }\r
234                 return -1;\r
235             }\r
236             yPos += rect.height;\r
237             sPos += sourceLength;\r
238         }\r
239 \r
240         return -1;\r
241     }\r
242 \r
243     /**\r
244      * {@inheritDoc}\r
245      * @param appendable {@inheritDoc}\r
246      * @return {@inheritDoc}\r
247      * @throws java.io.IOException {@inheritDoc}\r
248      */\r
249     public Appendable appendSelected(Appendable appendable)\r
250             throws IOException{\r
251         if(this.selectStart < 0 || this.selectLast < 0) return appendable;\r
252         CharSequence subsel;\r
253         subsel = this.source.subSequence(this.selectStart,\r
254                                          this.selectLast + 1);\r
255         appendable.append(subsel);\r
256         return appendable;\r
257     }\r
258 \r
259     /**\r
260      * {@inheritDoc}\r
261      */\r
262     public void clearSelect(){\r
263         this.selectStart = -1;\r
264         this.selectLast  = -1;\r
265         return;\r
266     }\r
267 \r
268     /**\r
269      * 指定した部分文字列を選択された状態にする。\r
270      * @param start 文字列開始位置\r
271      * @param last 文字列終了位置\r
272      */\r
273     public void select(int start, int last){\r
274         if(start < last){\r
275             this.selectStart = start;\r
276             this.selectLast  = last;\r
277         }else{\r
278             this.selectStart = last;\r
279             this.selectLast  = start;\r
280         }\r
281         this.selectLast  = Math.min(this.source.length() - 1,\r
282                                     this.selectLast );\r
283         return;\r
284     }\r
285 \r
286     /**\r
287      * {@inheritDoc}\r
288      * @param from {@inheritDoc}\r
289      * @param to {@inheritDoc}\r
290      */\r
291     public void drag(Point from, Point to){\r
292         Point fromPt = from;\r
293         Point toPt = to;\r
294         if(fromPt.y > toPt.y || (fromPt.y == toPt.y && fromPt.x > toPt.x)){\r
295             Point swapPt = fromPt;\r
296             fromPt = toPt;\r
297             toPt = swapPt;\r
298         }\r
299 \r
300         int fromDirection = GUIUtils.getDirection(this.bounds, fromPt);\r
301         int toDirection   = GUIUtils.getDirection(this.bounds, toPt);\r
302 \r
303         if(fromDirection == toDirection){\r
304             if(   fromDirection == NORTH\r
305                || fromDirection == SOUTH){\r
306                 clearSelect();\r
307                 return;\r
308             }\r
309         }\r
310 \r
311         int fromIndex = -1;\r
312         int toIndex   = -1;\r
313 \r
314         if(fromDirection == NORTH){\r
315             fromIndex = 0;\r
316         }\r
317         if(toDirection == SOUTH){\r
318             toIndex = this.source.length() - 1;\r
319         }\r
320 \r
321         if(fromIndex < 0){\r
322             fromIndex = getCharIndex(fromPt);\r
323         }\r
324         if(toIndex < 0){\r
325             toIndex = getCharIndex(toPt);\r
326         }\r
327 \r
328         if(fromIndex >= 0 && toIndex >= 0){\r
329             select(fromIndex, toIndex);\r
330             return;\r
331         }\r
332 \r
333         int xPos = this.bounds.x;\r
334         int yPos = this.bounds.y;\r
335         int accumPos = 0;\r
336         for(GlyphVector gv : this.lines){\r
337             int glyphStart = accumPos;\r
338             int glyphLast   = accumPos + gv.getNumGlyphs() - 1;\r
339             Rectangle2D r2d = gv.getLogicalBounds();\r
340             Rectangle rect = r2d.getBounds();\r
341             rect.x += xPos;\r
342             rect.y = yPos;\r
343 \r
344             if(   fromIndex < 0\r
345                && GUIUtils.getDirection(rect, fromPt) == SOUTH){\r
346                 yPos += rect.height;\r
347                 accumPos = glyphLast + 1;\r
348                 continue;\r
349             }else if(   toIndex < 0\r
350                      && GUIUtils.getDirection(rect, toPt) == NORTH){\r
351                 break;\r
352             }\r
353 \r
354             if(fromIndex < 0){\r
355                 int dir = GUIUtils.getDirection(rect, fromPt);\r
356                 if(dir == EAST){\r
357                     fromIndex = glyphStart;\r
358                 }else if(dir == WEST){\r
359                     fromIndex = glyphLast+1;\r
360                 }\r
361             }\r
362             if(toIndex < 0){\r
363                 int dir = GUIUtils.getDirection(rect, toPt);\r
364                 if(dir == EAST){\r
365                     toIndex = glyphStart - 1;\r
366                 }else if(dir == WEST){\r
367                     toIndex = glyphLast;\r
368                 }\r
369             }\r
370 \r
371             if(fromIndex >= 0 && toIndex >= 0){\r
372                 select(fromIndex, toIndex);\r
373                 return;\r
374             }\r
375 \r
376             yPos += rect.height;\r
377             accumPos = glyphLast + 1;\r
378         }\r
379 \r
380         clearSelect();\r
381         return;\r
382     }\r
383 \r
384     /**\r
385      * 文字列検索がヒットした箇所のハイライト描画を行う。\r
386      * @param g グラフィックスコンテキスト\r
387      */\r
388     private void paintRegexHitted(Graphics2D g){\r
389         if(this.matchList.size() <= 0) return;\r
390 \r
391         FontMetrics metrics = g.getFontMetrics();\r
392         final int ascent  = metrics.getAscent();\r
393 \r
394         int xPos = this.bounds.x;\r
395         int yPos = this.bounds.y + ascent;\r
396 \r
397         int accumPos = 0;\r
398 \r
399         for(GlyphVector line : this.lines){\r
400             int glyphStart = accumPos;\r
401             int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
402 \r
403             for(MatchInfo match : this.matchList){\r
404                 int matchStart = match.getStartPos();\r
405                 int matchLast  = match.getEndPos() - 1;\r
406 \r
407                 if(matchLast < glyphStart) continue;\r
408                 if(glyphLast < matchStart) break;\r
409 \r
410                 int hilightStart = Math.max(matchStart, glyphStart);\r
411                 int hilightLast  = Math.min(matchLast,  glyphLast);\r
412                 Shape shape;\r
413                 shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
414                 Rectangle hilight = shape.getBounds();\r
415                 shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
416                 hilight.add(shape.getBounds());\r
417 \r
418                 if(match == this.hotTarget){\r
419                     g.setColor(COLOR_HOTTARGET);\r
420                 }else{\r
421                     g.setColor(COLOR_SEARCHHIT);\r
422                 }\r
423 \r
424                 g.fillRect(xPos + hilight.x,\r
425                            yPos + hilight.y,\r
426                            hilight.width,\r
427                            hilight.height );\r
428             }\r
429 \r
430             Rectangle2D r2d = line.getLogicalBounds();\r
431             Rectangle rect = r2d.getBounds();\r
432 \r
433             yPos += rect.height;\r
434 \r
435             accumPos = glyphLast + 1;\r
436         }\r
437 \r
438         return;\r
439     }\r
440 \r
441     /**\r
442      * 選択文字列のハイライト描画を行う。\r
443      * @param g グラフィックスコンテキスト\r
444      */\r
445     private void paintSelected(Graphics2D g){\r
446         if(this.selectStart < 0 || this.selectLast < 0) return;\r
447 \r
448         g.setColor(COLOR_SELECTION);\r
449 \r
450         int xPos = this.bounds.x;\r
451         int yPos = this.bounds.y;\r
452 \r
453         int accumPos = 0;\r
454 \r
455         for(GlyphVector line : this.lines){\r
456             int glyphStart = accumPos;\r
457             int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
458 \r
459             if(this.selectLast < glyphStart) break;\r
460 \r
461             Rectangle2D r2d = line.getLogicalBounds();\r
462             Rectangle rect = r2d.getBounds();\r
463 \r
464             if(glyphLast < this.selectStart){\r
465                 yPos += rect.height;\r
466                 accumPos = glyphLast + 1;\r
467                 continue;\r
468             }\r
469 \r
470             int hilightStart = Math.max(this.selectStart, glyphStart);\r
471             int hilightLast  = Math.min(this.selectLast,  glyphLast);\r
472             Shape shape;\r
473             shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
474             Rectangle hilight = shape.getBounds();\r
475             shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
476             hilight.add(shape.getBounds());\r
477 \r
478             g.fillRect(xPos + hilight.x,\r
479                        yPos,\r
480                        hilight.width,\r
481                        hilight.height );\r
482 \r
483             yPos += rect.height;\r
484             accumPos = glyphLast + 1;\r
485         }\r
486 \r
487         return;\r
488     }\r
489 \r
490     /**\r
491      * アンカー文字列のハイライト描画を行う。\r
492      * @param g グラフィックスコンテキスト\r
493      */\r
494     private void paintAnchorBack(Graphics2D g){\r
495         if(this.anchorSet == null) return;\r
496         if(this.anchorSet.size() <= 0) return;\r
497 \r
498         FontMetrics metrics = g.getFontMetrics();\r
499         final int ascent  = metrics.getAscent();\r
500 \r
501         g.setColor(Color.GRAY);\r
502 \r
503         int xPos = this.bounds.x;\r
504         int yPos = this.bounds.y + ascent;\r
505 \r
506         int accumPos = 0;\r
507 \r
508         for(GlyphVector line : this.lines){\r
509             int glyphStart = accumPos;\r
510             int glyphLast   = accumPos + line.getNumGlyphs() - 1;\r
511 \r
512             for(Anchor anchor : this.anchorSet){\r
513                 int anchorStart = anchor.getStartPos();\r
514                 int anchorLast   = anchor.getEndPos() - 1;\r
515 \r
516                 if(anchorLast < glyphStart) continue;\r
517                 if(glyphLast < anchorStart) break;\r
518 \r
519                 int hilightStart = Math.max(anchorStart, glyphStart);\r
520                 int hilightLast  = Math.min(anchorLast,  glyphLast);\r
521                 Shape shape;\r
522                 shape = line.getGlyphLogicalBounds(hilightStart - glyphStart);\r
523                 Rectangle hilight = shape.getBounds();\r
524                 shape = line.getGlyphLogicalBounds(hilightLast - glyphStart);\r
525                 hilight.add(shape.getBounds());\r
526 \r
527                 g.fillRect(xPos + hilight.x,\r
528                            yPos + hilight.y,\r
529                            hilight.width,\r
530                            hilight.height );\r
531             }\r
532 \r
533             Rectangle2D r2d = line.getLogicalBounds();\r
534             Rectangle rect = r2d.getBounds();\r
535 \r
536             yPos += rect.height;\r
537 \r
538             accumPos = glyphLast + 1;\r
539         }\r
540 \r
541         return;\r
542     }\r
543 \r
544     /**\r
545      * {@inheritDoc}\r
546      * @param g {@inheritDoc}\r
547      */\r
548     public void paint(Graphics2D g){\r
549         g.setFont(this.fontInfo.getFont());\r
550         FontMetrics metrics = g.getFontMetrics();\r
551         int ascent  = metrics.getAscent();\r
552 \r
553         int xPos = this.bounds.x;\r
554         int yPos = this.bounds.y + ascent;\r
555 \r
556         paintAnchorBack(g);\r
557         paintRegexHitted(g);\r
558         paintSelected(g);\r
559 \r
560         g.setColor(this.foregroundColor);\r
561         for(GlyphVector gv : this.lines){\r
562             g.drawGlyphVector(gv, xPos, yPos);\r
563 \r
564             Rectangle2D r2d = gv.getLogicalBounds();\r
565             Rectangle rect = r2d.getBounds();\r
566 \r
567             yPos += rect.height;\r
568         }\r
569 \r
570         return;\r
571     }\r
572 \r
573     /**\r
574      * 与えられた座標にアンカー文字列が存在すればAnchorを返す。\r
575      * @param pt 座標\r
576      * @return アンカー\r
577      */\r
578     public Anchor getAnchor(Point pt){\r
579         int targetIdx = getCharIndex(pt);\r
580         if(targetIdx < 0) return null;\r
581 \r
582         for(Anchor anchor : this.anchorSet){\r
583             int anchorStart = anchor.getStartPos();\r
584             int anchorEnd   = anchor.getEndPos();\r
585             if(anchorStart <= targetIdx && targetIdx <= anchorEnd - 1){\r
586                 return anchor;\r
587             }\r
588         }\r
589 \r
590         return null;\r
591     }\r
592 \r
593     /**\r
594      * 与えられた座標に検索マッチ文字列があればそのインデックスを返す。\r
595      * @param pt 座標\r
596      * @return 検索マッチインデックス\r
597      */\r
598     public int getRegexMatchIndex(Point pt){\r
599         int targetIdx = getCharIndex(pt);\r
600         if(targetIdx < 0) return -1;\r
601 \r
602         int index = 0;\r
603         for(MatchInfo info : this.matchList){\r
604             int matchStart = info.getStartPos();\r
605             int matchEnd   = info.getEndPos();\r
606             if(matchStart <= targetIdx && targetIdx <= matchEnd - 1){\r
607                 return index;\r
608             }\r
609             index++;\r
610         }\r
611 \r
612         return -1;\r
613     }\r
614 \r
615     /**\r
616      * 検索文字列パターンを設定する。\r
617      * @param searchRegex パターン\r
618      * @return ヒット数\r
619      */\r
620     public int setRegex(Pattern searchRegex){\r
621         clearHotTarget();\r
622         this.matchList.clear();\r
623         if(searchRegex == null) return 0;\r
624 \r
625         Matcher matcher = searchRegex.matcher(this.source);\r
626         while(matcher.find()){\r
627             int startPos = matcher.start();\r
628             int endPos   = matcher.end();\r
629             if(startPos >= endPos) break;  // 長さ0マッチは無視\r
630             MatchInfo matchInfo = new MatchInfo(startPos, endPos);\r
631             this.matchList.add(matchInfo);\r
632         }\r
633 \r
634         return getRegexMatches();\r
635     }\r
636 \r
637     /**\r
638      * 検索ハイライトインデックスを返す。\r
639      * @return 検索ハイライトインデックス。見つからなければ-1。\r
640      */\r
641     public int getHotTargetIndex(){\r
642         return this.matchList.indexOf(this.hotTarget);\r
643     }\r
644 \r
645     /**\r
646      * 検索ハイライトを設定する。\r
647      * @param index ハイライトインデックス。負ならハイライト全クリア。\r
648      */\r
649     public void setHotTargetIndex(int index){\r
650         if(index < 0){\r
651             clearHotTarget();\r
652             return;\r
653         }\r
654         this.hotTarget = this.matchList.get(index);\r
655         return;\r
656     }\r
657 \r
658     /**\r
659      * 検索一致件数を返す。\r
660      * @return 検索一致件数\r
661      */\r
662     public int getRegexMatches(){\r
663         return this.matchList.size();\r
664     }\r
665 \r
666     /**\r
667      * 特別な検索ハイライト描画をクリアする。\r
668      */\r
669     public void clearHotTarget(){\r
670         this.hotTarget = null;\r
671         return;\r
672     }\r
673 \r
674     /**\r
675      * 特別な検索ハイライト領域の寸法を返す。\r
676      * @return ハイライト領域寸法\r
677      */\r
678     public Rectangle getHotTargetRectangle(){\r
679         Rectangle result = null;\r
680 \r
681         if(this.hotTarget == null) return result;\r
682 \r
683         int xPos = this.bounds.x;\r
684         int yPos = this.bounds.y;\r
685 \r
686         int accumPos = 0;\r
687 \r
688         int matchStart = this.hotTarget.getStartPos();\r
689         int matchLast  = this.hotTarget.getEndPos() - 1;\r
690 \r
691         for(GlyphVector gv : this.lines){\r
692             int glyphStart = accumPos;\r
693             int glyphLast   = accumPos + gv.getNumGlyphs() - 1;\r
694 \r
695             if(matchLast < glyphStart) break;\r
696 \r
697             if(matchStart <= glyphLast){\r
698                 int hilightStart = Math.max(matchStart, glyphStart);\r
699                 int hilightLast  = Math.min(matchLast,  glyphLast);\r
700 \r
701                 Shape shape;\r
702                 shape = gv.getGlyphLogicalBounds(hilightStart - glyphStart);\r
703                 Rectangle hilight = shape.getBounds();\r
704                 shape = gv.getGlyphLogicalBounds(hilightLast - glyphStart);\r
705                 hilight.add(shape.getBounds());\r
706 \r
707                 Rectangle temp = new Rectangle(xPos + hilight.x,\r
708                                                yPos,\r
709                                                hilight.width,\r
710                                                hilight.height);\r
711                 if(result == null){\r
712                     result = temp;\r
713                 }else{\r
714                     result.add(temp);\r
715                 }\r
716             }\r
717 \r
718             Rectangle2D r2d = gv.getLogicalBounds();\r
719             Rectangle rect = r2d.getBounds();\r
720             yPos += rect.height;\r
721 \r
722             accumPos = glyphLast + 1;\r
723         }\r
724 \r
725         return result;\r
726     }\r
727 \r
728     /**\r
729      * 検索ヒット情報。\r
730      */\r
731     private static class MatchInfo{\r
732 \r
733         private final int startPos;\r
734         private final int endPos;\r
735 \r
736         /**\r
737          * コンストラクタ。\r
738          * @param startPos ヒット開始位置\r
739          * @param endPos ヒット終了位置\r
740          */\r
741         public MatchInfo(int startPos, int endPos){\r
742             super();\r
743             this.startPos = startPos;\r
744             this.endPos   = endPos;\r
745             return;\r
746         }\r
747 \r
748         /**\r
749          * ヒット開始位置を取得する。\r
750          * @return ヒット開始位置\r
751          */\r
752         public int getStartPos(){\r
753             return this.startPos;\r
754         }\r
755 \r
756         /**\r
757          * ヒット終了位置を取得する。\r
758          * @return ヒット終了位置\r
759          */\r
760         public int getEndPos(){\r
761             return this.endPos;\r
762         }\r
763     }\r
764 \r
765 }\r