]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/richtext/textformat/BidiParagraphRenderer.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / richtext / textformat / BidiParagraphRenderer.java
1 /*\r
2  * (C) Copyright IBM Corp. 1998-2007.  All Rights Reserved.\r
3  *\r
4  * The program is provided "as is" without any warranty express or\r
5  * implied, including the warranty of non-infringement and the implied\r
6  * warranties of merchantibility and fitness for a particular purpose.\r
7  * IBM will not be liable for any damages suffered by you as a result\r
8  * of using the Program. In no event will IBM be liable for any\r
9  * special, indirect or consequential damages or lost profits even if\r
10  * IBM has been advised of the possibility of their occurrence. IBM\r
11  * will not be liable for any third party claims against you.\r
12  */\r
13 // Requires Java2\r
14 package com.ibm.richtext.textformat;\r
15 \r
16 import java.awt.Color;\r
17 import java.awt.Rectangle;\r
18 import java.awt.Shape;\r
19 \r
20 import java.util.Vector;\r
21 \r
22 import com.ibm.richtext.styledtext.MConstText;\r
23 import com.ibm.richtext.styledtext.MTabRuler;\r
24 import com.ibm.richtext.styledtext.TabStop;\r
25 \r
26 import com.ibm.richtext.textlayout.attributes.AttributeMap;\r
27 import com.ibm.richtext.textlayout.attributes.TextAttribute;\r
28 \r
29 import com.ibm.richtext.textlayout.Graphics2DConversion;\r
30 \r
31 ///*JDK12IMPORTS\r
32 import java.awt.Graphics2D;\r
33 \r
34 import java.awt.font.FontRenderContext;\r
35 import java.awt.font.TextLayout;\r
36 import java.awt.font.LineBreakMeasurer;\r
37 import java.awt.font.TextHitInfo;\r
38 \r
39 import java.awt.geom.AffineTransform;\r
40 import java.awt.geom.GeneralPath;\r
41 import java.awt.geom.Rectangle2D;\r
42 //JDK12IMPORTS*/\r
43 /*JDK11IMPORTS\r
44 import com.ibm.richtext.textlayout.Graphics2D;\r
45 \r
46 import com.ibm.richtext.textlayout.FontRenderContext;\r
47 import com.ibm.richtext.textlayout.TextLayout;\r
48 import com.ibm.richtext.textlayout.LineBreakMeasurer;\r
49 import com.ibm.richtext.textlayout.TextHitInfo;\r
50 \r
51 import com.ibm.richtext.textlayout.AffineTransform;\r
52 import com.ibm.richtext.textlayout.GeneralPath;\r
53 import com.ibm.richtext.textlayout.Rectangle2D;\r
54 JDK11IMPORTS*/\r
55 \r
56 final class BidiParagraphRenderer extends ParagraphRenderer {\r
57 \r
58     static final String COPYRIGHT =\r
59                 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";\r
60     private final class BidiSegment {\r
61         TextLayout fLayout;\r
62         Rectangle2D.Float fBounds;\r
63         int fDistanceFromLeadingMargin;\r
64     }\r
65 \r
66     private final class BidiLayoutInfo extends LayoutInfo\r
67     {                    \r
68         int fCharLength;      // number of characters on line (was fLength)\r
69         int fAscent;\r
70         int fDescent;\r
71         int fLeading;\r
72         int fVisibleAdvance;  // distance along line direction ie width\r
73         int fTotalAdvance;    // distance along line direction including trailing whitespace\r
74 \r
75         int fLeadingMargin;   // screen distance from leading margin\r
76 \r
77         boolean fLeftToRight; // true iff the orientation is left-to-right\r
78 \r
79         final Vector fSegments = new Vector(); // segments to render, in logical order\r
80 \r
81         public int getCharLength() {\r
82             return fCharLength;\r
83         }\r
84 \r
85         public int getAscent() {\r
86             return fAscent;\r
87         }\r
88 \r
89         public int getDescent() {\r
90             return fDescent;\r
91         }\r
92 \r
93         public int getLeading() {\r
94             return fLeading;\r
95         }\r
96 \r
97         public int getVisibleAdvance() {\r
98             return fVisibleAdvance;\r
99         }\r
100 \r
101         public int getTotalAdvance() {\r
102             return fTotalAdvance;\r
103         }\r
104 \r
105         public int getLeadingMargin() {\r
106             return fLeadingMargin;\r
107         }\r
108 \r
109         public boolean isLeftToRight() {\r
110             return fLeftToRight;\r
111         }\r
112 \r
113         public int getHeight() {\r
114             return fAscent + fDescent + fLeading;\r
115         }\r
116 \r
117         public String toString()\r
118         {\r
119             return "LayoutInfo(charStart: " + getCharStart(0) +\r
120                 ", fCharLength: " + fCharLength +\r
121                 ", fAscent: " + fAscent +\r
122                 ", fDescent: " + fDescent +\r
123                 ", fVisibleAdvance: " + fVisibleAdvance +\r
124                 ", fTotalAdvance: " + fTotalAdvance +\r
125                 ", fLeadingMargin: " + fLeadingMargin +\r
126                 ")";\r
127         }\r
128 \r
129         BidiParagraphRenderer fRenderer;\r
130 \r
131         // just delegate to renderer for now\r
132 \r
133         public void renderWithHighlight(int lengthBasis,\r
134                                         Graphics2D g,\r
135                                         int lineBound,\r
136                                         int x,\r
137                                         int y,\r
138                                         TextOffset selStart,\r
139                                         TextOffset selStop,\r
140                                         Color highlightColor) {\r
141 \r
142             fRenderer.renderWithHighlight(this,\r
143                                           lengthBasis,\r
144                                           g,\r
145                                           lineBound,\r
146                                           x,\r
147                                           y,\r
148                                           selStart,\r
149                                           selStop,\r
150                                           highlightColor);\r
151         }\r
152 \r
153         public void render(int lengthBasis,\r
154                            Graphics2D g,\r
155                            int lineBound,\r
156                            int x,\r
157                            int y) {\r
158             fRenderer.render(this, lengthBasis, g, lineBound, x, y);\r
159         }\r
160 \r
161         public void renderCaret(MConstText text,\r
162                                 int lengthBasis,\r
163                                 Graphics2D g,\r
164                                 int lineBound,\r
165                                 int x,\r
166                                 int y,\r
167                                 int charOffset,\r
168                                 Color strongCaretColor,\r
169                                 Color weakCaretColor) {\r
170             fRenderer.renderCaret(this, text, lengthBasis, g, lineBound, x, y, charOffset,\r
171                                         strongCaretColor, weakCaretColor);\r
172         }\r
173 \r
174         public TextOffset pixelToOffset(int lengthBasis,\r
175                                         TextOffset result,\r
176                                         int lineBound,\r
177                                         int x,\r
178                                         int y) {\r
179             return fRenderer.pixelToOffset(this, lengthBasis, result, lineBound, x, y);\r
180         }\r
181 \r
182         public Rectangle caretBounds(MConstText text,\r
183                                      int lengthBasis,\r
184                                      int lineBound,\r
185                                      int charOffset,\r
186                                      int x,\r
187                                      int y) {\r
188             return fRenderer.caretBounds(this, text, lengthBasis, lineBound, charOffset, x, y);\r
189         }\r
190         \r
191         public int strongCaretBaselinePosition(int lengthBasis,\r
192                                                int lineBound,\r
193                                                int charOffset) {\r
194 \r
195             return fRenderer.strongCaretBaselinePosition(this, lengthBasis, lineBound, charOffset);\r
196         }\r
197 \r
198         public int getNextOffset(int lengthBasis,\r
199                                  int charOffset,\r
200                                  short dir) {\r
201 \r
202             return fRenderer.getNextOffset(this, lengthBasis, charOffset, dir);\r
203         }\r
204     }\r
205 \r
206     //private static final int FLUSH_LEADING = TextAttribute.FLUSH_LEADING.intValue();\r
207     private static final int FLUSH_CENTER = TextAttribute.FLUSH_CENTER.intValue();\r
208     private static final int FLUSH_TRAILING = TextAttribute.FLUSH_TRAILING.intValue();\r
209     private static final int FULLY_JUSTIFIED = TextAttribute.FULLY_JUSTIFIED.intValue();\r
210 \r
211     private AttributeMap cacheStyle = null;\r
212 \r
213     private float fLeadingMargin;\r
214     private float fTrailingMargin;\r
215     private float fFirstLineIndent;\r
216     private float fMinLineSpacing;\r
217     private float fExtraLineSpacing;\r
218     \r
219     private int fFlush = -1;\r
220     private MTabRuler fTabRuler;\r
221     \r
222     private boolean fLtrDefault;\r
223     private DefaultCharacterMetric fDefaultCharMetric;\r
224     \r
225     BidiParagraphRenderer(AttributeMap pStyle, DefaultCharacterMetric defaultCharMetric) {\r
226 \r
227         fDefaultCharMetric = defaultCharMetric;\r
228         initRenderer(pStyle);\r
229     }\r
230 \r
231     private float getFloatValue(Object key, AttributeMap style) {\r
232         return ((Float)style.get(key)).floatValue();\r
233     }\r
234     \r
235     private int getIntValue(Object key, AttributeMap style) {\r
236         return ((Integer)style.get(key)).intValue();\r
237     }\r
238     \r
239     /**\r
240      * NOTE:  it is illegal to initialize a StandardParagraphRenderer for any style\r
241      * other than the one it was created with.\r
242      */\r
243     public void initRenderer(AttributeMap pStyle) {\r
244 \r
245         if (cacheStyle == null) {\r
246 \r
247             fLeadingMargin = getFloatValue(TextAttribute.LEADING_MARGIN, pStyle);\r
248             fTrailingMargin = getFloatValue(TextAttribute.TRAILING_MARGIN, pStyle);\r
249             fFirstLineIndent = getFloatValue(TextAttribute.FIRST_LINE_INDENT, pStyle);\r
250             fMinLineSpacing = getFloatValue(TextAttribute.MIN_LINE_SPACING, pStyle);\r
251             fExtraLineSpacing = getFloatValue(TextAttribute.EXTRA_LINE_SPACING, pStyle);\r
252 \r
253             fFlush = getIntValue(TextAttribute.LINE_FLUSH, pStyle);\r
254 \r
255             fTabRuler = (MTabRuler) pStyle.get(TextAttribute.TAB_RULER);\r
256             \r
257             Object runDir = pStyle.get(TextAttribute.RUN_DIRECTION);\r
258             fLtrDefault = !TextAttribute.RUN_DIRECTION_RTL.equals(runDir);\r
259 \r
260             cacheStyle = pStyle;\r
261         }\r
262         else if (pStyle != cacheStyle) {\r
263             if (!pStyle.equals(cacheStyle)) {\r
264                 throw new Error("Attempt to share BidiParagraphRenderer between styles!");\r
265             }\r
266             else {\r
267                 cacheStyle = pStyle;\r
268             }\r
269         }\r
270     }\r
271 \r
272     private static boolean isTab(char ch) {\r
273         return ch == '\t';\r
274     }\r
275 \r
276     /**\r
277      * Fill in info with the next line.\r
278      * @param measurer the LineBreakMeasurer for this paragraph.\r
279      *  Current position should be the first character on the line.\r
280      *  If null, a 0-length line is generated.  If measurer is null\r
281      *  then paragraphStart and paragraphLimit should be equal.\r
282      */\r
283     // Usually totalFormatWidth and lineBound will be the same.\r
284     // totalFormatWidth is used for wrapping, but lineBound is\r
285     // for flushing.  These may be different for unwrapped text,\r
286     // for example.\r
287     public LayoutInfo layout(MConstText text,\r
288                              LayoutInfo layoutToReuse,\r
289                              LineBreakMeasurer measurer,\r
290                              FontRenderContext frc,\r
291                              int paragraphStart,\r
292                              int paragraphLimit,\r
293                              int totalFormatWidth,\r
294                              int lineBound) {\r
295 \r
296         if ((measurer==null) != (paragraphStart==paragraphLimit)) {\r
297             throw new IllegalArgumentException(\r
298                     "measurer, paragraphStart, paragraphLimit are wrong.");\r
299         }\r
300         BidiLayoutInfo line = null;\r
301 \r
302         try {\r
303             line = (BidiLayoutInfo) layoutToReuse;\r
304         }\r
305         catch(ClassCastException e) {\r
306         }\r
307 \r
308         if (line == null) {\r
309             line = new BidiLayoutInfo();\r
310         }\r
311 \r
312         line.fRenderer = this;\r
313 \r
314         final int lineCharStart = measurer==null? paragraphStart : measurer.getPosition();\r
315         line.setCharStart(lineCharStart);\r
316 \r
317         final int lineIndent = (lineCharStart==paragraphStart)? (int) fFirstLineIndent : 0;\r
318 \r
319         int formatWidth = totalFormatWidth - (int) (fLeadingMargin + fTrailingMargin);\r
320         computeLineMetrics(text, line, measurer, frc,\r
321                             paragraphStart, paragraphLimit, formatWidth, lineIndent);\r
322 \r
323         // position the line according to the line flush\r
324         if (fFlush == FLUSH_TRAILING || fFlush == FLUSH_CENTER) {\r
325             int lineArea = lineBound - (int) (fLeadingMargin + fTrailingMargin);\r
326             int advanceDifference = lineArea - line.fVisibleAdvance;\r
327 \r
328             if (fFlush == FLUSH_TRAILING) {\r
329                 line.fLeadingMargin = ((int) (fLeadingMargin)) + advanceDifference;\r
330             }\r
331             else if (fFlush == FLUSH_CENTER) {\r
332                 line.fLeadingMargin = (int) (fLeadingMargin + advanceDifference/2);\r
333             }\r
334         }\r
335         else {\r
336             line.fLeadingMargin = (int) fLeadingMargin;\r
337         }\r
338 \r
339         return line;\r
340     }\r
341 \r
342     /**\r
343      * Fill in the following fields in line:\r
344      * fCharLength, fAscent, fDescent, fLeading, fVisibleAdvance,\r
345      * fTotalAdvance.\r
346      * Uses: line.fLeadingMargin\r
347      * @param formatWidth the width to fit the line into.\r
348      */\r
349     private void computeLineMetrics(MConstText text,\r
350                                     BidiLayoutInfo line,\r
351                                     LineBreakMeasurer measurer,\r
352                                     FontRenderContext frc,\r
353                                     final int paragraphStart,\r
354                                     final int paragraphLimit,\r
355                                     final int formatWidth,\r
356                                     final int lineIndent) {\r
357 \r
358         int segmentCount = 0;\r
359         /* variable not used boolean firstLine = measurer==null ||\r
360                             measurer.getPosition() == paragraphStart; */\r
361 \r
362         if (measurer != null) {\r
363             computeSegments(text, line, measurer, paragraphLimit, formatWidth, lineIndent);\r
364 \r
365             // iterate through segments and accumulate ascent, descent,\r
366             // leading, char length\r
367             float ascent = 0;\r
368             float descent = 0;\r
369             float descentPlusLeading = 0;\r
370 \r
371             segmentCount = line.fSegments.size();\r
372             for (int i=0; i < segmentCount; i++) {\r
373                 TextLayout layout = ((BidiSegment)line.fSegments.elementAt(i)).fLayout;\r
374                 ascent = Math.max(ascent, layout.getAscent());\r
375                 float segDescent = layout.getDescent();\r
376                 descent = Math.max(descent, segDescent);\r
377                 descentPlusLeading = Math.max(descentPlusLeading, segDescent+layout.getLeading());\r
378                 line.fCharLength += layout.getCharacterCount();\r
379             }\r
380 \r
381             line.fAscent = (int) Math.ceil(ascent);\r
382             line.fDescent = (int) Math.ceil(descent);\r
383             line.fLeading = (int) Math.ceil(descentPlusLeading) - line.fDescent;\r
384         }\r
385         else {\r
386             line.fLeftToRight = fLtrDefault;\r
387             line.fSegments.removeAllElements();\r
388 \r
389             line.fCharLength = 0;\r
390 \r
391             AttributeMap style = text.characterStyleAt(paragraphStart);\r
392             DefaultCharacterMetric.Metric cm = fDefaultCharMetric.getMetricForStyle(style);\r
393             line.fAscent = cm.getAscent();\r
394             line.fDescent = cm.getDescent();\r
395             line.fLeading = cm.getLeading();\r
396 \r
397             line.fVisibleAdvance = line.fTotalAdvance = 0;\r
398         }\r
399 \r
400         if (fExtraLineSpacing != 0) {\r
401             line.fAscent += (int) Math.ceil(fExtraLineSpacing);\r
402         }\r
403 \r
404         if (fMinLineSpacing != 0){\r
405             int height = line.getHeight();\r
406             if (height < fMinLineSpacing) {\r
407                 line.fAscent += Math.ceil(fMinLineSpacing - height);\r
408             }\r
409         }\r
410 \r
411         final int lineNaturalAdvance = line.fTotalAdvance;\r
412 \r
413         line.fTotalAdvance += lineIndent;\r
414         line.fVisibleAdvance += lineIndent;\r
415 \r
416         if (measurer != null) {\r
417             // Now fill in fBounds field of BidiSegments.  fBounds should tile\r
418             // the line.\r
419             final float lineHeight = line.getHeight();\r
420 \r
421             for (int i=1; i < segmentCount; i++) {\r
422 \r
423                 BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(i-1);\r
424                 BidiSegment nextSegment = (BidiSegment) line.fSegments.elementAt(i);\r
425 \r
426                 float origin;\r
427                 float width;\r
428 \r
429                 if (line.fLeftToRight) {\r
430                     origin = 0;\r
431                     width = nextSegment.fDistanceFromLeadingMargin -\r
432                                 currentSegment.fDistanceFromLeadingMargin;\r
433                 }\r
434                 else {\r
435                     origin = currentSegment.fDistanceFromLeadingMargin;\r
436                     origin -= nextSegment.fDistanceFromLeadingMargin;\r
437                     origin += (float) Math.ceil(nextSegment.fLayout.getAdvance());\r
438                     width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;\r
439                 }\r
440                 currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);\r
441             }\r
442 \r
443             // set last segment's bounds\r
444             {\r
445                 BidiSegment currentSegment = (BidiSegment) line.fSegments.elementAt(segmentCount-1);\r
446                 float origin;\r
447                 float width;\r
448 \r
449                 if (line.fLeftToRight) {\r
450                     origin = 0;\r
451                     width = lineNaturalAdvance - currentSegment.fDistanceFromLeadingMargin;\r
452                 }\r
453                 else {\r
454                     origin = currentSegment.fDistanceFromLeadingMargin - lineNaturalAdvance;\r
455                     width = (float) Math.ceil(currentSegment.fLayout.getAdvance()) - origin;\r
456                 }\r
457 \r
458                 currentSegment.fBounds = new Rectangle2D.Float(origin, -line.fAscent, width, lineHeight);\r
459             }\r
460         }\r
461     }\r
462 \r
463     /**\r
464      * Fill in fSegments, fLeftToRight.  measurer must not be null\r
465      */\r
466     private void computeSegments(MConstText text,\r
467                                  BidiLayoutInfo line,\r
468                                  LineBreakMeasurer measurer,\r
469                                  final int paragraphLimit,\r
470                                  final int formatWidth,\r
471                                  final int lineIndent) {\r
472 \r
473         // Note on justification:  only the last segment of a line is\r
474         // justified.  \r
475         // Also, if a line ends in a tab it will not be justified.\r
476         // This behavior is consistent with other word processors\r
477         // I tried (MS Word and Lotus Word Pro).\r
478         \r
479         line.fSegments.removeAllElements();\r
480         line.fCharLength = 0;\r
481 \r
482         TabStop currentTabStop = new TabStop((int)fLeadingMargin+lineIndent, TabStop.kLeading);\r
483 \r
484         int segmentLimit = measurer.getPosition();\r
485         boolean firstSegment = true;\r
486 \r
487         int advanceFromLeadingMargin = lineIndent;\r
488 \r
489         boolean computeSegs = true;\r
490         \r
491         computeTabbedSegments: do {\r
492 \r
493             // compute sementLimit:\r
494             if (segmentLimit <= measurer.getPosition()) {\r
495                 while (segmentLimit < paragraphLimit) {\r
496                     if (isTab(text.at(segmentLimit++))) {\r
497                         break;\r
498                     }\r
499                 }\r
500             }\r
501 \r
502             // NOTE:  adjust available width for center tab!!!\r
503             //System.out.println("Format width: " + (formatWidth-advanceFromLeadingMargin) +\r
504             //                   ";  segmentLimit: " + segmentLimit);\r
505 \r
506             int wrappingWidth = Math.max(formatWidth-advanceFromLeadingMargin, 0);\r
507             TextLayout layout = null;\r
508             if (firstSegment || wrappingWidth > 0 || segmentLimit > measurer.getPosition()+1) {\r
509                 layout = measurer.nextLayout(wrappingWidth, segmentLimit, !firstSegment);\r
510             }\r
511 \r
512             if (layout == null) {\r
513                 if (firstSegment) {\r
514                     // I doubt this would happen, but check anyway\r
515                     throw new Error("First layout is null!");\r
516                 }\r
517                 break computeTabbedSegments;\r
518             }\r
519             \r
520             final int measurerPos = measurer.getPosition();\r
521             if (measurerPos < segmentLimit) {\r
522                 computeSegs = false;\r
523                 if (fFlush == FULLY_JUSTIFIED) {\r
524                     layout = layout.getJustifiedLayout(wrappingWidth);\r
525                 }\r
526             }\r
527             else {\r
528                 computeSegs = !(measurerPos == paragraphLimit);\r
529             }\r
530 \r
531             if (firstSegment) {\r
532                 firstSegment = false;\r
533                 // Have to get ltr off of layout.  Not available from measurer,\r
534                 // unfortunately.\r
535                 line.fLeftToRight = layout.isLeftToRight();\r
536             }\r
537 \r
538             BidiSegment segment = new BidiSegment();\r
539             segment.fLayout = layout;\r
540             int layoutAdvance = (int) Math.ceil(layout.getAdvance());\r
541 \r
542             // position layout relative to leading margin, update logicalPositionOnLine\r
543             \r
544             int relativeTabPosition = currentTabStop.getPosition()-(int)fLeadingMargin;\r
545             int logicalPositionOfLayout;\r
546             switch (currentTabStop.getType()) {\r
547                 case TabStop.kTrailing:\r
548                     logicalPositionOfLayout = Math.max(\r
549                                     relativeTabPosition-layoutAdvance,\r
550                                     advanceFromLeadingMargin);\r
551                     break;\r
552                 case TabStop.kCenter:\r
553                     logicalPositionOfLayout = Math.max(\r
554                                     relativeTabPosition-(layoutAdvance/2),\r
555                                     advanceFromLeadingMargin);\r
556                     break;\r
557                 default:  // includes decimal tab right now\r
558                     logicalPositionOfLayout = relativeTabPosition;\r
559                     break;\r
560             }\r
561 \r
562             // position layout in segment\r
563             if (line.fLeftToRight) {\r
564                 segment.fDistanceFromLeadingMargin = logicalPositionOfLayout;\r
565             }\r
566             else {\r
567                 segment.fDistanceFromLeadingMargin = logicalPositionOfLayout+layoutAdvance;\r
568             }\r
569 \r
570             // update advanceFromLeadingMargin\r
571             advanceFromLeadingMargin = logicalPositionOfLayout + layoutAdvance;\r
572 \r
573             // add segment to segment Vector\r
574             line.fSegments.addElement(segment);\r
575 \r
576             // get next tab\r
577             currentTabStop = fTabRuler.nextTab((int)fLeadingMargin+advanceFromLeadingMargin);\r
578             if (currentTabStop.getType() == TabStop.kLeading ||\r
579                             currentTabStop.getType() == TabStop.kAuto)  {\r
580                 advanceFromLeadingMargin = currentTabStop.getPosition();\r
581                 //System.out.println("Advance from leading margin:" + advanceFromLeadingMargin);\r
582 \r
583             }\r
584             else {\r
585                //System.out.println("Non-leading tab, type=" + currentTabStop.getType());\r
586             }\r
587 \r
588         } while (computeSegs);\r
589 \r
590         // Now compute fTotalAdvance, fVisibleAdvance.  These metrics may be affected\r
591         // by a trailing tab.\r
592 \r
593         {\r
594             BidiSegment lastSegment = (BidiSegment) line.fSegments.lastElement();\r
595             TextLayout lastLayout = lastSegment.fLayout;\r
596 \r
597             if (line.fLeftToRight) {\r
598                 line.fTotalAdvance = (int) Math.ceil(lastLayout.getAdvance()) +\r
599                                         lastSegment.fDistanceFromLeadingMargin;\r
600                 line.fVisibleAdvance = (int) Math.ceil(lastLayout.getVisibleAdvance()) +\r
601                                         lastSegment.fDistanceFromLeadingMargin;\r
602             }\r
603             else {\r
604                 line.fTotalAdvance = lastSegment.fDistanceFromLeadingMargin;\r
605                 line.fVisibleAdvance = lastSegment.fDistanceFromLeadingMargin -\r
606                                         (int) Math.ceil(lastLayout.getAdvance() -\r
607                                             lastLayout.getVisibleAdvance());\r
608             }\r
609 \r
610             if (isTab(text.at(measurer.getPosition()-1))) {\r
611                 line.fTotalAdvance = Math.max(line.fTotalAdvance,\r
612                                                 currentTabStop.getPosition());\r
613             }\r
614         }\r
615     }\r
616 \r
617     /**\r
618      * Return the highlight shape for the given character offsets.\r
619      * The Shape returned is relative to the leftmost point on the\r
620      * baseline of line.\r
621      */\r
622     private Shape getHighlightShape(BidiLayoutInfo line,\r
623                                     int lengthBasis,\r
624                                     int lineBound,\r
625                                     int hlStart,\r
626                                     int hlLimit) {\r
627 \r
628         if (hlStart >= hlLimit) {\r
629             throw new IllegalArgumentException("Highlight range length is not positive.");\r
630         }\r
631 \r
632         final int leadingMargin = (line.fLeftToRight)?\r
633                 line.fLeadingMargin : lineBound - line.fLeadingMargin;\r
634         final int segmentCount = line.fSegments.size();\r
635 \r
636         Shape rval = null;\r
637         GeneralPath highlightPath = null;\r
638 \r
639         int currentLayoutStart = line.getCharStart(lengthBasis);\r
640 \r
641         for (int i=0; i < segmentCount; i++) {\r
642 \r
643             BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);\r
644             TextLayout layout = segment.fLayout;\r
645             int charCount = layout.getCharacterCount();\r
646             int currentLayoutLimit = currentLayoutStart + charCount;\r
647             boolean rangesIntersect;\r
648             if (hlStart <= currentLayoutStart) {\r
649                 rangesIntersect = hlLimit > currentLayoutStart;\r
650             }\r
651             else {\r
652                 rangesIntersect = hlStart < currentLayoutLimit;\r
653             }\r
654 \r
655             if (rangesIntersect) {\r
656 \r
657                 Shape currentHl = layout.getLogicalHighlightShape(\r
658                                         Math.max(hlStart-currentLayoutStart, 0),\r
659                                         Math.min(hlLimit-currentLayoutStart, charCount),\r
660                                         segment.fBounds);\r
661 \r
662                 float xTranslate;\r
663                 if (line.fLeftToRight) {\r
664                     xTranslate = leadingMargin +\r
665                                  segment.fDistanceFromLeadingMargin;\r
666                 }\r
667                 else {\r
668                     xTranslate = leadingMargin -\r
669                                  segment.fDistanceFromLeadingMargin;\r
670                 }\r
671 \r
672                 if (xTranslate != 0) {\r
673                     AffineTransform xform =\r
674                         AffineTransform.getTranslateInstance(xTranslate, 0);\r
675                     currentHl = xform.createTransformedShape(currentHl);\r
676                 }\r
677 \r
678                 if (rval == null) {\r
679                     rval = currentHl;\r
680                 }\r
681                 else {\r
682                     if (highlightPath == null) {\r
683                         highlightPath = new GeneralPath();\r
684                         highlightPath.append(rval, false);\r
685                         rval = highlightPath;\r
686                     }\r
687                     highlightPath.append(currentHl, false);\r
688                 }\r
689             }\r
690             currentLayoutStart = currentLayoutLimit;\r
691         }\r
692 \r
693         return rval;\r
694     }\r
695 \r
696     private void renderWithHighlight(BidiLayoutInfo line,\r
697                                      int lengthBasis,\r
698                                      Graphics2D g,\r
699                                      int lineBound,\r
700                                      int x,\r
701                                      int y,\r
702                                      TextOffset selStart,\r
703                                      TextOffset selStop,\r
704                                      Color highlightColor) {\r
705 \r
706         final int lineCharStart = line.getCharStart(lengthBasis);\r
707 \r
708         if (selStart != null && selStop != null && !selStart.equals(selStop) &&\r
709                 line.fCharLength != 0 &&\r
710                 selStart.fOffset < lineCharStart + line.fCharLength &&\r
711                 selStop.fOffset > lineCharStart) {\r
712 \r
713             Shape highlight = getHighlightShape(line, lengthBasis, lineBound, selStart.fOffset, selStop.fOffset);\r
714             if (highlight != null) {\r
715                 Graphics2D hl = (Graphics2D) g.create();\r
716                 hl.setColor(highlightColor);\r
717                 hl.translate(x, y + line.fAscent);\r
718                 hl.fill(highlight);\r
719             }\r
720         }\r
721 \r
722         render(line, lengthBasis, g, lineBound, x, y);\r
723     }\r
724 \r
725     /**\r
726      * Draw the line into the graphics.  (x, y) is the upper-left corner\r
727      * of the line.  The leading edge of a right-aligned line is aligned\r
728      * to (x + lineBound).\r
729      */\r
730     private void render(BidiLayoutInfo line,\r
731                         int lengthBasis,\r
732                         Graphics2D g,\r
733                         int lineBound,\r
734                         int x,\r
735                         int y) {\r
736 \r
737         final int leadingMargin = (line.fLeftToRight)?\r
738                 x + line.fLeadingMargin : x + lineBound - line.fLeadingMargin;\r
739         final int baseline = y + line.fAscent;\r
740         final int segmentCount = line.fSegments.size();\r
741 \r
742         for (int i=0; i < segmentCount; i++) {\r
743 \r
744             BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);\r
745 \r
746             float drawX;\r
747             if (line.fLeftToRight) {\r
748                 drawX = leadingMargin + segment.fDistanceFromLeadingMargin;\r
749             }\r
750             else {\r
751                 drawX = leadingMargin - segment.fDistanceFromLeadingMargin;\r
752             }\r
753 \r
754             segment.fLayout.draw(g, drawX, baseline);\r
755         }\r
756     }\r
757 \r
758     private TextOffset hitTestSegment(TextOffset result,\r
759                                       int segmentCharStart,\r
760                                       BidiSegment segment,\r
761                                       int xInSegment,\r
762                                       int yInSegment) {\r
763 \r
764         final TextLayout layout = segment.fLayout;\r
765         final int charCount = layout.getCharacterCount();\r
766         final int layoutAdvance = (int) Math.ceil(layout.getAdvance());\r
767         Rectangle2D bounds = segment.fBounds;\r
768 \r
769         final boolean ltr = layout.isLeftToRight();\r
770 \r
771         if (ltr && (xInSegment >= layoutAdvance) || !ltr && (xInSegment <= 0)) {\r
772 \r
773             // pretend the extra space at the end of the line is a\r
774             // tab and 'hit-test' it.\r
775             double tabCenter;\r
776             if (ltr) {\r
777                 tabCenter = (layoutAdvance+bounds.getMaxX()) / 2;\r
778             }\r
779             else {\r
780                 tabCenter = bounds.getX() / 2;\r
781             }\r
782 \r
783             if ((xInSegment >= tabCenter) == ltr) {\r
784                 result.fOffset = charCount;\r
785                 result.fPlacement = TextOffset.BEFORE_OFFSET;\r
786             }\r
787             else {\r
788                 result.fOffset = charCount-1;\r
789                 result.fPlacement = TextOffset.AFTER_OFFSET;\r
790             }\r
791         }\r
792         else {\r
793             TextHitInfo info = layout.hitTestChar(xInSegment, yInSegment, segment.fBounds);\r
794             result.fOffset = info.getInsertionIndex();\r
795             if (result.fOffset == 0) {\r
796                 result.fPlacement = TextOffset.AFTER_OFFSET;\r
797             }\r
798             else if (result.fOffset == charCount) {\r
799                 result.fPlacement = TextOffset.BEFORE_OFFSET;\r
800             }\r
801             else {\r
802                 result.fPlacement = info.isLeadingEdge()?\r
803                         TextOffset.AFTER_OFFSET : TextOffset.BEFORE_OFFSET;\r
804             }\r
805         }\r
806 \r
807         result.fOffset += segmentCharStart;\r
808         return result;\r
809     }\r
810 \r
811     /**\r
812      * Return the offset at the point (x, y).  (x, y) is relative to the top-left\r
813      * of the line.  The leading edge of a right-aligned line is aligned\r
814      * to lineBound.\r
815      */\r
816     private TextOffset pixelToOffset(BidiLayoutInfo line,\r
817                                      int lengthBasis,\r
818                                      TextOffset result,\r
819                                      int lineBound,\r
820                                      int x,\r
821                                      int y) {\r
822 \r
823         if (result == null) {\r
824             result = new TextOffset();\r
825         }\r
826 \r
827         final int yInSegment = y - line.fAscent;\r
828         final int leadingMargin = (line.fLeftToRight)?\r
829                 line.fLeadingMargin : lineBound - line.fLeadingMargin;\r
830         final int lineCharStart = line.getCharStart(lengthBasis);\r
831 \r
832         // first see if point is before leading edge of line\r
833         final int segmentCount = line.fSegments.size();\r
834         {\r
835             int segLeadingMargin = leadingMargin;\r
836             if (segmentCount > 0) {\r
837                 BidiSegment firstSeg = (BidiSegment) line.fSegments.elementAt(0);\r
838                 if (line.fLeftToRight) {\r
839                     segLeadingMargin += firstSeg.fDistanceFromLeadingMargin;\r
840                 }\r
841                 else {\r
842                     segLeadingMargin -= firstSeg.fDistanceFromLeadingMargin;\r
843                     segLeadingMargin += (float) firstSeg.fBounds.getMaxX();\r
844                 }\r
845             }\r
846             if (line.fLeftToRight == (x <= segLeadingMargin)) {\r
847                 result.fOffset = lineCharStart;\r
848                 result.fPlacement = TextOffset.AFTER_OFFSET;\r
849                 return result;\r
850             }\r
851         }\r
852 \r
853         int segmentCharStart = lineCharStart;\r
854 \r
855         for (int i=0; i < segmentCount; i++) {\r
856 \r
857             BidiSegment segment = (BidiSegment) line.fSegments.elementAt(i);\r
858             int segmentOrigin = line.fLeftToRight?\r
859                             leadingMargin+segment.fDistanceFromLeadingMargin :\r
860                             leadingMargin-segment.fDistanceFromLeadingMargin;\r
861             int xInSegment = x - segmentOrigin;\r
862             if (line.fLeftToRight) {\r
863                 if (segment.fBounds.getMaxX() > xInSegment) {\r
864                     return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);\r
865                 }\r
866             }\r
867             else {\r
868                 if (segment.fBounds.getX() < xInSegment) {\r
869                     return hitTestSegment(result, segmentCharStart, segment, xInSegment, yInSegment);\r
870                 }\r
871             }\r
872             segmentCharStart += segment.fLayout.getCharacterCount();\r
873         }\r
874 \r
875         result.fOffset = lineCharStart + line.fCharLength;\r
876         result.fPlacement = TextOffset.BEFORE_OFFSET;\r
877         return result;\r
878     }\r
879 \r
880     private void renderCaret(BidiLayoutInfo line,\r
881                              MConstText text,\r
882                              int lengthBasis,\r
883                              Graphics2D g,\r
884                              int lineBound,\r
885                              int x,\r
886                              int y,\r
887                              final int charOffset,\r
888                              Color strongCaretColor,\r
889                              Color weakCaretColor)\r
890     {\r
891         final int segmentCount = line.fSegments.size();\r
892         final int lineStart = line.getCharStart(lengthBasis);\r
893 \r
894         int currentStart = lineStart;\r
895         BidiSegment segment = null;\r
896         int segmentIndex;\r
897 \r
898         for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {\r
899             segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
900             int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();\r
901             if (currentEndpoint > charOffset) {\r
902                 break;\r
903             }\r
904             currentStart = currentEndpoint;\r
905         }\r
906 \r
907         /*\r
908             There are two choices here:\r
909             1. get carets from a TextLayout and render them, or\r
910             2. make up a caret ourselves and render it.\r
911             We want to do 2 when:\r
912                 * there is no text on the line, or\r
913                 * the line ends with a tab and we are drawing the last caret on the line\r
914             Otherwise, we want 1.\r
915         */\r
916 \r
917         if (segmentIndex == segmentCount && segmentCount > 0) {\r
918             // If we get here, line length is not 0, and charOffset is at end of line\r
919             if (!isTab(text.at(charOffset-1))) {\r
920                 segmentIndex = segmentCount-1;\r
921                 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
922                 currentStart = lineStart + line.getCharLength() -\r
923                                     segment.fLayout.getCharacterCount();\r
924             }\r
925         }\r
926 \r
927         Object savedPaint = Graphics2DConversion.getColorState(g);\r
928 \r
929         try {\r
930             if (segmentIndex < segmentCount) {\r
931                 TextLayout layout = segment.fLayout;\r
932                 int offsetInLayout = charOffset - currentStart;\r
933                 Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);\r
934                 g.setColor(strongCaretColor);\r
935                 int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;\r
936                 int layoutX = line.fLeftToRight?\r
937                         x + layoutPos : x + lineBound - layoutPos;\r
938                 int layoutY = y + line.fAscent;\r
939 \r
940                 // Translating and then clipping doesn't work.  Try this:\r
941                 Rectangle2D.Float clipRect = new Rectangle2D.Float();\r
942                 clipRect.setRect(segment.fBounds);\r
943                 clipRect.x += layoutX;\r
944                 clipRect.y += layoutY;\r
945                 clipRect.width += 1;\r
946                 clipRect.height -= 1;\r
947 \r
948                 Object savedClip = ClipWorkaround.saveClipState(g);\r
949                 try {\r
950                     ClipWorkaround.translateAndDrawShapeWithClip(g,\r
951                                                                 layoutX,\r
952                                                                 layoutY,\r
953                                                                 clipRect,\r
954                                                                 carets[0]);\r
955                     if (carets[1] != null) {\r
956                         g.setColor(weakCaretColor);\r
957                         ClipWorkaround.translateAndDrawShapeWithClip(g,\r
958                                                                     layoutX,\r
959                                                                     layoutY,\r
960                                                                     clipRect,\r
961                                                                     carets[1]);\r
962                     }\r
963                 }\r
964                 finally {\r
965                     ClipWorkaround.restoreClipState(g, savedClip);\r
966                 }\r
967             }\r
968             else {\r
969                 int lineEnd = line.fLeadingMargin + line.fTotalAdvance;\r
970                 int endX = line.fLeftToRight? lineEnd : lineBound-lineEnd;\r
971                 endX += x;\r
972                 g.drawLine(endX, y, endX, y+line.getHeight()-1);\r
973             }\r
974         }\r
975         finally {\r
976             Graphics2DConversion.restoreColorState(g, savedPaint);\r
977         }\r
978     }\r
979 \r
980     private Rectangle caretBounds(BidiLayoutInfo line,\r
981                                   MConstText text,\r
982                                   int lengthBasis,\r
983                                   int lineBound,\r
984                                   int charOffset,\r
985                                   int x,\r
986                                   int y) {\r
987 \r
988         final int segmentCount = line.fSegments.size();\r
989         final int lineStart = line.getCharStart(lengthBasis);\r
990         int currentStart = lineStart;\r
991         BidiSegment segment = null;\r
992         int segmentIndex;\r
993 \r
994         for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {\r
995             segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
996             int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();\r
997             if (currentEndpoint > charOffset) {\r
998                 break;\r
999             }\r
1000             currentStart = currentEndpoint;\r
1001         }\r
1002 \r
1003         if (segmentIndex == segmentCount && segmentCount > 0) {\r
1004             // If we get here, line length is not 0, and charOffset is at end of line\r
1005             if (!isTab(text.at(charOffset-1))) {\r
1006                 segmentIndex = segmentCount-1;\r
1007                 segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
1008                 currentStart = lineStart + line.getCharLength() -\r
1009                                     segment.fLayout.getCharacterCount();\r
1010             }\r
1011         }\r
1012 \r
1013         Rectangle r;\r
1014         \r
1015         if (segmentIndex < segmentCount) {\r
1016             TextLayout layout = segment.fLayout;\r
1017             int offsetInLayout = charOffset - currentStart;\r
1018             Shape[] carets = layout.getCaretShapes(offsetInLayout, segment.fBounds);\r
1019             r = carets[0].getBounds();\r
1020             if (carets[1] != null) {\r
1021                 r.add(carets[1].getBounds());\r
1022             }\r
1023             r.width += 1;\r
1024             \r
1025             int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;\r
1026             if (line.fLeftToRight) {\r
1027                 r.x += layoutPos;\r
1028             }\r
1029             else {\r
1030                 r.x += lineBound - layoutPos;\r
1031             }\r
1032             r.y += line.fAscent;\r
1033         }\r
1034         else {\r
1035             r = new Rectangle();\r
1036             r.height = line.getHeight();\r
1037             r.width = 1;\r
1038             int lineEnd = line.fLeadingMargin + line.fTotalAdvance;\r
1039             if (line.fLeftToRight) {\r
1040                 r.x = lineEnd;\r
1041             }\r
1042             else {\r
1043                 r.x = lineBound - lineEnd;\r
1044             }\r
1045         }\r
1046         \r
1047         r.translate(x, y);\r
1048         return r;\r
1049     }\r
1050 \r
1051     private int strongCaretBaselinePosition(BidiLayoutInfo line,\r
1052                                             int lengthBasis,\r
1053                                             int lineBound,\r
1054                                             int charOffset) {\r
1055 \r
1056         final int segmentCount = line.fSegments.size();\r
1057         int currentStart = line.getCharStart(lengthBasis);\r
1058         BidiSegment segment = null;\r
1059         int segmentIndex;\r
1060 \r
1061         for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {\r
1062             segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
1063             int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();\r
1064             if (currentEndpoint > charOffset) {\r
1065                 break;\r
1066             }\r
1067             currentStart = currentEndpoint;\r
1068         }\r
1069 \r
1070         if (segmentIndex < segmentCount) {\r
1071             TextLayout layout = segment.fLayout;\r
1072             int offsetInLayout = charOffset - currentStart;\r
1073             TextHitInfo hit = TextHitInfo.afterOffset(offsetInLayout);\r
1074             hit = TextLayout.DEFAULT_CARET_POLICY.getStrongCaret(hit, hit.getOtherHit(), layout);\r
1075             float[] info = layout.getCaretInfo(hit);\r
1076             int layoutPos = line.fLeadingMargin + segment.fDistanceFromLeadingMargin;\r
1077             if (line.fLeftToRight) {\r
1078                 return layoutPos + (int) info[0];\r
1079             }\r
1080             else {\r
1081                 return lineBound - layoutPos + (int) info[0];\r
1082             }\r
1083         }\r
1084         else {\r
1085             int lineEnd = line.fLeadingMargin + line.fTotalAdvance;\r
1086             if (line.fLeftToRight) {\r
1087                 return lineEnd;\r
1088             }\r
1089             else {\r
1090                 return lineBound - lineEnd;\r
1091             }\r
1092         }\r
1093     }\r
1094 \r
1095     private int getNextOffset(BidiLayoutInfo line,\r
1096                               int lengthBasis,\r
1097                               int charOffset,\r
1098                               short dir) {\r
1099 \r
1100         if (dir != MFormatter.eLeft && dir != MFormatter.eRight) {\r
1101             throw new IllegalArgumentException("Invalid direction.");\r
1102         }\r
1103 \r
1104         // find segment containing offset:\r
1105         final int segmentCount = line.fSegments.size();\r
1106         final int lineCharStart = line.getCharStart(lengthBasis);\r
1107 \r
1108         int currentStart = lineCharStart;\r
1109         BidiSegment segment = null;\r
1110         int segmentIndex;\r
1111 \r
1112         for (segmentIndex=0; segmentIndex < segmentCount; segmentIndex++) {\r
1113             segment = (BidiSegment) line.fSegments.elementAt(segmentIndex);\r
1114             int currentEndpoint = currentStart + segment.fLayout.getCharacterCount();\r
1115             if (currentEndpoint > charOffset ||\r
1116                     (segmentIndex == segmentCount-1 && currentEndpoint==charOffset)) {\r
1117                 break;\r
1118             }\r
1119             currentStart = currentEndpoint;\r
1120         }\r
1121 \r
1122         final boolean logAdvance = (dir==MFormatter.eRight)==(line.fLeftToRight);\r
1123 \r
1124         int result;\r
1125 \r
1126         if (segmentIndex < segmentCount) {\r
1127             TextLayout layout = segment.fLayout;\r
1128             int offsetInLayout = charOffset - currentStart;\r
1129             TextHitInfo hit = (dir==MFormatter.eLeft)?\r
1130                         layout.getNextLeftHit(offsetInLayout) :\r
1131                         layout.getNextRightHit(offsetInLayout);\r
1132             if (hit == null) {\r
1133                 result = logAdvance?\r
1134                     currentStart+layout.getCharacterCount()+1 : currentStart-1;\r
1135             }\r
1136             else {\r
1137                 result = hit.getInsertionIndex() + currentStart;\r
1138             }\r
1139         }\r
1140         else {\r
1141             result = logAdvance? lineCharStart + line.fCharLength + 1 :\r
1142                                          lineCharStart - 1;\r
1143         }\r
1144 \r
1145         return result;\r
1146     }\r
1147 }