KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > batik > extension > svg > FlowExtGlyphLayout


1 /*
2
3    Copyright 2004 The Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    You may obtain a copy of the License at
8
9        http://www.apache.org/licenses/LICENSE-2.0
10
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16
17 */

18
19 package org.apache.batik.extension.svg;
20
21 import java.awt.geom.Point2D JavaDoc;
22 import java.awt.font.FontRenderContext JavaDoc;
23 import java.text.AttributedCharacterIterator JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.LinkedList JavaDoc;
27
28 import org.apache.batik.gvt.font.GVTFont;
29 import org.apache.batik.gvt.font.GVTGlyphVector;
30 import org.apache.batik.gvt.font.GVTLineMetrics;
31 import org.apache.batik.gvt.font.MultiGlyphVector;
32 import org.apache.batik.gvt.text.GlyphLayout;
33
34 import org.apache.batik.gvt.TextNode;
35
36 /**
37  * A GlyphLayout class for SVG 1.2 flowing text.
38  *
39  * @author <a HREF="mailto:deweese@apache.org">deweese</a>
40  * @version $Id: FlowExtGlyphLayout.java,v 1.2 2005/03/27 08:58:33 cam Exp $
41  */

42 public class FlowExtGlyphLayout extends GlyphLayout {
43
44     public FlowExtGlyphLayout(AttributedCharacterIterator JavaDoc aci,
45                            int [] charMap,
46                            Point2D JavaDoc offset,
47                            FontRenderContext JavaDoc frc) {
48         super(aci, charMap, offset, frc);
49     }
50
51
52     // Issues:
53
// Should the font size of non-printing chars affect line spacing?
54
// Does line breaking get done before/after ligatures?
55
// What should be done if the next glyph does not fit in the
56
// flow rect (very narrow flow rect).
57
// Print the one char anyway.
58
// Go to the next flow rect.
59
// Should dy be considered for line offsets? (super scripts)
60
// Should p's & br's carry over from flow rect to flow rect if
61
// so how much????
62

63     // In cases where 1/2 leading is negative (lineBox is smaller than
64
// lineAscent+lineDescent) do we use the lineBox (some glyphs will
65
// go outside flowRegion) or the visual box. My feeling is that
66
// we should use the larger of the two.
67

68     // We stated that for empty para elements it moves to the new flow
69
// region if the zero-height line is outside the flow region. In
70
// this case the para elements top-margin is used in the new flow
71
// region (and it's bottom-margin is collapsed with the next
72
// flowPara element if any). What happens when the first line of
73
// a non-empty flowPara doesn't fit (so the top margin does fit
74
// but the first line of text doesn't). I think the para should
75
// move to the next flow region and the top margin should apply in
76
// the new flow region. The top margin does not apply if
77
// subsequint lines move to a new flow region.
78

79     // Note that line wrapping is done on visual bounds of glyph
80
// not the glyph advance (which often includes some whitespace
81
// after the right edge of the glyph char).
82

83     //
84
// How are Margins done? Can't figure out Box size until
85
// after we know the margins.
86
// Should 'A' element be allowed in 'flowPara'.
87
//
88
// For Full justification:
89
// Streach glyphs to fill line? (attribute?)
90
// What to do with partial line (last line in 'p', 'line'
91
// element, or 'div' element), still full justify, just left
92
// justify, attribute?
93
// What to do when only one glyph on line? left or center or stretch?
94
// For full to look good I think the line must be able to squeeze a
95
// bit as well as grow (pretty easy to add).
96
//
97
// This Only does horizontal languages.
98
// Supports Zero Width Spaces (0x200B) Zero Width Joiner( 0x200D),
99
// and soft hyphens (0x00AD).
100
//
101
// Does not properly handle Bi-DI languages (does text wrapping on
102
// display order not logical order).
103

104     /**
105      * This will wrap the text associated with <tt>aci</tt> and
106      * <tt>layouts</tt>.
107      * @param acis An array of Attributed Charater Iterators containing the
108      * text to wrap. There is one aci per text chunk
109      * (which maps to flowPara elements. Used to access
110      * font, paragraph, and line break info.
111      * @param chunkLayouts A List of List of GlyphLayout objects. There
112      * is a List of GlyphLayout objects for each
113      * flowPara element. There is a GlyphLayout
114      * for approximately each sub element in the
115      * flowPara element.
116      * @param flowRects A List of Rectangle2D representing the regions
117      * to flow text into.
118      */

119     public static void textWrapTextChunk(AttributedCharacterIterator JavaDoc [] acis,
120                                          List JavaDoc chunkLayouts,
121                                          List JavaDoc flowRects) {
122         // System.out.println("Len: " + acis.length + " Size: " +
123
// chunkLayouts.size());
124

125         // Make a list of the GlyphVectors so we can construct a
126
// multiGlyphVector that makes them all look like one big
127
// glyphVector
128
GVTGlyphVector [] gvs = new GVTGlyphVector[acis.length];
129         List JavaDoc [] chunkLineInfos = new List JavaDoc [acis.length];
130         GlyphIterator [] gis = new GlyphIterator [acis.length];
131         Iterator JavaDoc clIter = chunkLayouts.iterator();
132
133         // Get an iterator for the flow rects.
134
Iterator JavaDoc flowRectsIter = flowRects.iterator();
135         // Get info for new flow rect.
136
RegionInfo currentRegion = null;
137         float y0, x0, width, height=0;
138         if (flowRectsIter.hasNext()) {
139             currentRegion = (RegionInfo) flowRectsIter.next();
140             height = (float) currentRegion.getHeight();
141         }
142
143         boolean lineHeightRelative = true;
144         float lineHeight = 1.0f;
145         float nextLineMult = 0.0f;
146         float dy = 0.0f;
147
148         //
149
Point2D.Float JavaDoc verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
150
151         //System.out.println("Chunks: " + numChunks);
152

153         float prevBotMargin = 0;
154         for (int chunk=0; clIter.hasNext(); chunk++) {
155             // System.out.println("Chunk: " + chunk);
156
AttributedCharacterIterator JavaDoc aci = acis[chunk];
157             if (currentRegion != null)
158             {
159                 List JavaDoc extraP = (List JavaDoc)aci.getAttribute(FLOW_EMPTY_PARAGRAPH);
160                 if (extraP != null) {
161                     Iterator JavaDoc epi = extraP.iterator();
162                     while (epi.hasNext()) {
163                         MarginInfo emi = (MarginInfo)epi.next();
164                         float inc = ((prevBotMargin > emi.getTopMargin())
165                                      ? prevBotMargin
166                                      : emi.getTopMargin());
167                         if ((dy + inc <= height) &&
168                             !emi.isFlowRegionBreak()) {
169                             dy += inc;
170                             prevBotMargin = emi.getBottomMargin();
171                         } else {
172                             // Move to next flow region..
173
if (!flowRectsIter.hasNext()) {
174                                 currentRegion = null;
175                                 break; // No flow rect stop layout here...
176
}
177
178                             // NEXT FLOW REGION
179
currentRegion = (RegionInfo) flowRectsIter.next();
180                             height = (float) currentRegion.getHeight();
181                             // start a new alignment offset for this flow rect.
182
verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
183
184                             // Don't use this paragraph info in next
185
// flow region!
186
dy = 0;
187                             prevBotMargin = 0;
188                         }
189                     }
190
191                     if (currentRegion == null) break;
192                 }
193             }
194
195             List JavaDoc gvl = new LinkedList JavaDoc();
196             List JavaDoc layouts = (List JavaDoc)clIter.next();
197             Iterator JavaDoc iter = layouts.iterator();
198             while (iter.hasNext()) {
199                 GlyphLayout gl = (GlyphLayout)iter.next();
200                 gvl.add(gl.getGlyphVector());
201             }
202             GVTGlyphVector gv = new MultiGlyphVector(gvl);
203             gvs[chunk] = gv;
204             int numGlyphs = gv.getNumGlyphs();
205
206             // System.out.println("Glyphs: " + numGlyphs);
207

208             aci.first();
209             MarginInfo mi = (MarginInfo)aci.getAttribute(FLOW_PARAGRAPH);
210             if (mi == null) {
211               continue;
212             }
213             // int justification = mi.getJustification();
214

215             if (currentRegion == null) {
216                 for(int idx=0; idx <numGlyphs; idx++)
217                     gv.setGlyphVisible(idx, false);
218                 continue;
219             }
220
221             float inc = ((prevBotMargin > mi.getTopMargin())
222                          ? prevBotMargin : mi.getTopMargin());
223             if (dy + inc <= height) {
224                 dy += inc;
225             } else {
226                 // Move to next flow region..
227
// NEXT FLOW REGION
228
if (!flowRectsIter.hasNext()) {
229                     currentRegion = null;
230                     break; // No flow rect stop layout here...
231
}
232
233                 // NEXT FLOW REGION
234
currentRegion = (RegionInfo) flowRectsIter.next();
235                 height = (float) currentRegion.getHeight();
236                 // start a new alignment offset for this flow rect..
237
verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
238
239                 // New rect so no previous row to consider...
240
dy = mi.getTopMargin();
241             }
242             prevBotMargin = mi.getBottomMargin();
243
244             float leftMargin = mi.getLeftMargin();
245             float rightMargin = mi.getRightMargin();
246             if (((GlyphLayout)layouts.get(0)).isLeftToRight()) {
247                 leftMargin += mi.getIndent();
248             } else {
249                 rightMargin += mi.getIndent();
250             }
251
252             x0 = (float) currentRegion.getX() + leftMargin;
253             y0 = (float) currentRegion.getY();
254             width = (float) (currentRegion.getWidth() -
255                              (leftMargin + rightMargin));
256             height = (float) currentRegion.getHeight();
257             
258             List JavaDoc lineInfos = new LinkedList JavaDoc();
259             chunkLineInfos[chunk] = lineInfos;
260
261             float prevDesc = 0.0f;
262             GlyphIterator gi = new GlyphIterator(aci, gv);
263             gis[chunk] = gi;
264
265             GlyphIterator breakGI = null, newBreakGI = null;
266
267             if (!gi.done() && !gi.isPrinting()) {
268                 // This will place any preceeding whitespace on an
269
// imaginary line that preceeds the real first line of
270
// the paragraph, also calculate the vertical
271
// alignment offset, this will be repeated until the
272
// last line in the flow rect.
273
updateVerticalAlignOffset(verticalAlignOffset,
274                                          currentRegion, dy);
275                lineInfos.add(gi.newLine
276                              (new Point2D.Float JavaDoc(x0, y0+dy),
277                               width, true, verticalAlignOffset));
278             }
279
280
281             GlyphIterator lineGI = gi.copy();
282             boolean firstLine = true;
283             while (!gi.done()) {
284                 boolean doBreak = false;
285                 boolean partial = false;
286
287                 if (gi.isPrinting() && (gi.getAdv() > width)) {
288                     if (breakGI == null) {
289                         // first char on line didn't fit.
290
// move to next flow rect.
291
if (!flowRectsIter.hasNext()) {
292                             currentRegion = null;
293                             gi = lineGI.copy(gi);
294                             break; // No flow rect stop layout here...
295
}
296
297                         // NEXT FLOW REGION
298
currentRegion = (RegionInfo) flowRectsIter.next();
299                         x0 = (float) currentRegion.getX() + leftMargin;
300                         y0 = (float) currentRegion.getY();
301                         width = (float) (currentRegion.getWidth() -
302                                         (leftMargin+rightMargin));
303                         height = (float) currentRegion.getHeight();
304                         // start a new alignment offset for this flow rect..
305
verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
306
307                         // New rect so no previous row to consider...
308
dy = firstLine ? mi.getTopMargin() : 0;
309                         prevDesc = 0;
310                         gi = lineGI.copy(gi);
311                         continue;
312                     }
313
314                     gi = breakGI.copy(gi); // Back up to break loc...
315

316                     nextLineMult = 1;
317                     doBreak = true;
318                     partial = false;
319                 } else if (gi.isLastChar()) {
320                     nextLineMult = 1;
321                     doBreak = true;
322                     partial = true;
323                 }
324                 int lnBreaks = gi.getLineBreaks();
325                 if (lnBreaks != 0) {
326                     if (doBreak)
327                         nextLineMult -= 1;
328                     nextLineMult += lnBreaks;
329                     doBreak = true;
330                     partial = true;
331                 }
332
333                 if (!doBreak) {
334                     // System.out.println("No Brk Adv: " + gi.getAdv());
335
// We don't need to break the line because of this glyph
336
// So we just check if we need to update our break loc.
337
if ((gi.isBreakChar()) ||
338                         (breakGI == null) ||
339                         (!breakGI.isBreakChar())) {
340                         // Make this the new break if curr char is a
341
// break char or we don't have any break chars
342
// yet, or our current break char is also not
343
// a break char.
344
newBreakGI = gi.copy(newBreakGI);
345                         gi.nextChar();
346                         if (gi.getChar() != GlyphIterator.ZERO_WIDTH_JOINER) {
347                             GlyphIterator tmpGI = breakGI;
348                             breakGI = newBreakGI;
349                             newBreakGI = tmpGI;
350                         }
351                     } else {
352                         gi.nextChar();
353                     }
354                     continue;
355                 }
356
357                 // System.out.println(" Brk Adv: " + gi.getAdv());
358

359                 // We will now attempt to break the line just
360
// after 'gi'.
361

362                 // Note we are trying to figure out where the current
363
// line is going to be placed (not the next line). We
364
// must wait until we have a potential line break so
365
// we know how tall the line is.
366

367                 // Get the nomial line advance based on the
368
// largest font we encountered on line...
369
float lineSize = gi.getMaxAscent()+gi.getMaxDescent();
370                 float lineBoxHeight;
371                 if (lineHeightRelative)
372                     lineBoxHeight = gi.getMaxFontSize()*lineHeight;
373                 else
374                     lineBoxHeight = lineHeight;
375                 float halfLeading = (lineBoxHeight-lineSize)/2;
376
377                 float ladv = prevDesc + halfLeading + gi.getMaxAscent();
378                 float newDesc = halfLeading + gi.getMaxDescent();
379
380                 dy += ladv;
381                 float bottomEdge = newDesc;
382                 if (newDesc < gi.getMaxDescent())
383                     bottomEdge = gi.getMaxDescent();
384
385                 if ((dy + bottomEdge) > height) {
386                     // The current Line doesn't fit in the
387
// current flow rectangle so we need to
388
// move line to the next flow rect.
389

390                     // System.out.println("Doesn't Fit: " + dy);
391

392                     if (!flowRectsIter.hasNext()) {
393                         currentRegion = null;
394                         gi = lineGI.copy(gi);
395                         break; // No flow rect stop layout here...
396
}
397
398                         // Remember how wide this rectangle is...
399
float oldWidth = width;
400
401                     // Get info for new flow rect.
402
currentRegion = (RegionInfo) flowRectsIter.next();
403                     x0 = (float) currentRegion.getX() + leftMargin;
404                     y0 = (float) currentRegion.getY();
405                     width = (float)(currentRegion.getWidth() -
406                                      (leftMargin+rightMargin));
407                     height = (float) currentRegion.getHeight();
408                     // start a new alignment offset for this flow rect..
409
verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
410
411                     // New rect so no previous row to consider...
412
dy = firstLine ? mi.getTopMargin() : 0;
413                     prevDesc = 0;
414                     // previous flows?
415

416                     if ((oldWidth > width) || (lnBreaks != 0)) {
417                         // need to back up to start of line...
418
gi = lineGI.copy(gi);
419                     }
420                     continue;
421                 }
422
423                 prevDesc = newDesc + (nextLineMult-1)*lineBoxHeight;
424                 nextLineMult = 0f;
425                 updateVerticalAlignOffset(verticalAlignOffset,
426                                           currentRegion, dy + bottomEdge);
427                 lineInfos.add(gi.newLine
428                               (new Point2D.Float JavaDoc(x0, y0 + dy), width, partial,
429                                verticalAlignOffset));
430
431                 // System.out.println("Fit: " + dy);
432
x0 -= leftMargin;
433                 width += leftMargin+rightMargin;
434
435                 leftMargin = mi.getLeftMargin();
436                 rightMargin = mi.getRightMargin();
437                 x0 += leftMargin;
438                 width -= leftMargin+rightMargin;
439
440                 firstLine = false;
441                 // The line fits in the current flow rectangle.
442
lineGI = gi.copy(lineGI);
443                 breakGI = null;
444             }
445             dy += prevDesc;
446
447             int idx = gi.getGlyphIndex();
448             while(idx <numGlyphs)
449                 gv.setGlyphVisible(idx++, false);
450
451             if (mi.isFlowRegionBreak()) {
452                 // Move to next flow region..
453
currentRegion = null;
454                 if (flowRectsIter.hasNext()) {
455                     currentRegion = (RegionInfo) flowRectsIter.next();
456                     height = (float) currentRegion.getHeight();
457
458                     // Don't use this paragraph's info in next
459
// flow region!
460
dy = 0;
461                     prevBotMargin = 0;
462                     verticalAlignOffset = new Point2D.Float JavaDoc(0,0);
463                 }
464             }
465         }
466
467         for (int chunk=0; chunk < acis.length; chunk++) {
468             List JavaDoc lineInfos = chunkLineInfos[chunk];
469             if (lineInfos == null) continue;
470
471             AttributedCharacterIterator JavaDoc aci = acis[chunk];
472             aci.first();
473             MarginInfo mi = (MarginInfo)aci.getAttribute(FLOW_PARAGRAPH);
474             if (mi == null) {
475               continue;
476             }
477             int justification = mi.getJustification();
478             
479             GVTGlyphVector gv = gvs[chunk];
480             if (gv == null) break;
481
482             GlyphIterator gi = gis[chunk];
483             
484             layoutChunk(gv, gi.getOrigin(), justification, lineInfos);
485         }
486     }
487
488
489     /**
490      * Updates the specified verticalAlignmentOffset using the current
491      * alignment rule and the heights of the flow rect and the maximum
492      * descent of the text. This method gets for called every line,
493      * but only the value that is calculated for the last line of the
494      * flow rect is used by the glyph rendering. This is achieved by
495      * creating a new verticalAlignOffset object everytime a new flow
496      * rect is encountered, thus a single verticalAlignmentOffset is
497      * shared for all {@link LineInfo} objects created for a given
498      * flow rect. The value is calculated by determining the left
499      * over space in the flow rect and scaling that value by 1.0 to
500      * align to the bottom, 0.5 for middle and 0.0 for top.
501      *
502      * @param verticalAlignOffset the {@link java.awt.geom.Point2D.Float}
503      * object that is storing the alignment offset.
504      * @param region the {@link RegionInfo} object that we are rendering into.
505      * @param maxDescent the very lowest point this line reaches.
506      */

507     public static void updateVerticalAlignOffset
508         (Point2D.Float JavaDoc verticalAlignOffset,
509          RegionInfo region, float maxDescent)
510         {
511             float freeSpace = (float)region.getHeight() - maxDescent;
512             verticalAlignOffset.setLocation
513                 (0, region.getVerticalAlignment() * freeSpace);
514         }
515
516     public static void layoutChunk(GVTGlyphVector gv, Point2D JavaDoc origin,
517                                    int justification,
518                                    List JavaDoc lineInfos) {
519         Iterator JavaDoc lInfoIter = lineInfos.iterator();
520         int numGlyphs = gv.getNumGlyphs();
521         float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
522         Point2D.Float JavaDoc lineLoc = null;
523         float lineAdv = 0;
524         float lineVAdv = 0;
525
526         float xOrig=(float)origin.getX();
527         float yOrig=(float)origin.getY();
528
529         float xScale=1;
530         float xAdj=0;
531         float charW=0;
532         float lineWidth=0;
533         boolean partial = false;
534         float verticalAlignOffset = 0;
535
536         // This loop goes through and puts glyphs where they belong
537
// based on info collected in first trip through glyphVector...
538
int lineEnd = 0;
539         int i;
540         Point2D.Float JavaDoc pos = new Point2D.Float JavaDoc();
541         for (i =0; i<numGlyphs; i++) {
542             if (i == lineEnd) {
543                 // Always comes through here on first char...
544

545                 // Update offset for new line based on last line length
546
xOrig += lineAdv;
547
548                 // Get new values for everything...
549
if (!lInfoIter.hasNext())
550                     break;
551                 LineInfo li = (LineInfo)lInfoIter.next();
552                 // System.out.println(li.toString());
553

554                 lineEnd = li.getEndIdx();
555                 lineLoc = li.getLocation();
556                 lineAdv = li.getAdvance();
557                 lineVAdv = li.getVisualAdvance();
558                 charW = li.getLastCharWidth();
559                 lineWidth = li.getLineWidth();
560                 partial = li.isPartialLine();
561                 verticalAlignOffset = li.getVerticalAlignOffset().y;
562
563                 xAdj = 0;
564                 xScale = 1;
565                 // Recalc justification info.
566
switch (justification) {
567                 case 0: default: break; // Left
568
case 1: // Center
569
xAdj = (lineWidth - lineVAdv) / 2;
570                     break;
571                 case 2: // Right
572
xAdj = lineWidth - lineVAdv;
573                     break;
574                 case 3: // Full
575
if ((!partial) && (lineEnd != i+1)) {
576                         // More than one char on line...
577
// Scale char spacing to fill line.
578
xScale = (lineWidth-charW)/(lineVAdv-charW);
579                     }
580                     break;
581                 }
582             }
583             pos.x = lineLoc.x + (gp[2*i] -xOrig)*xScale+xAdj;
584             pos.y = lineLoc.y + ((gp[2 * i + 1] - yOrig) +
585                                  verticalAlignOffset);
586             gv.setGlyphPosition(i, pos);
587         }
588
589         pos.x = xOrig;
590         pos.y = yOrig;
591         if (lineLoc != null) {
592           pos.x = lineLoc.x + (gp[2*i] -xOrig)*xScale+xAdj;
593           pos.y = lineLoc.y + (gp[2 * i + 1] - yOrig) + verticalAlignOffset;
594         }
595         gv.setGlyphPosition(i, pos);
596     }
597 }
598
Popular Tags