KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > it > stefanochizzolini > clown > documents > contents > composition > BlockFilter


1 /*
2   Copyright © 2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
3
4   Contributors:
5     * Stefano Chizzolini (original code developer, info@stefanochizzolini.it):
6       contributed code is Copyright © 2007 by Stefano Chizzolini.
7
8   This file should be part of the source code distribution of "PDF Clown library"
9   (the Program): see the accompanying README files for more info.
10
11   This Program is free software; you can redistribute it and/or modify it under
12   the terms of the GNU General Public License as published by the Free Software
13   Foundation; either version 2 of the License, or (at your option) any later version.
14
15   This Program is distributed in the hope that it will be useful, but WITHOUT ANY
16   WARRANTY, either expressed or implied; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
18
19   You should have received a copy of the GNU General Public License along with this
20   Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
21
22   Redistribution and use, with or without modification, are permitted provided that such
23   redistributions retain the above copyright notice, license and disclaimer, along with
24   this list of conditions.
25 */

26
27 package it.stefanochizzolini.clown.documents.contents.composition;
28
29 import it.stefanochizzolini.clown.bytes.IBuffer;
30 import it.stefanochizzolini.clown.documents.contents.fonts.Font;
31 import it.stefanochizzolini.clown.objects.PdfReal;
32
33 import java.awt.geom.Dimension2D JavaDoc;
34 import java.awt.geom.Point2D JavaDoc;
35 import java.awt.geom.Rectangle2D JavaDoc;
36 import java.util.ArrayList JavaDoc;
37 import java.util.List JavaDoc;
38
39 /**
40   Content block filter.
41
42   @author Stefano Chizzolini
43   @version 0.0.3, 03/31/07
44   @since 0.0.3
45 */

46 /*
47   NOTE: This is just an experimental version of a content block filter.
48   TODO:IMPL: An improved version should seamlessly manage all the graphics parameters (especially
49   those text-related, like horizontal scaling etc.).
50 */

51 public class BlockFilter
52 {
53   // <class>
54
// <classes>
55
private static class Row
56   {
57     /**
58       Row's objects.
59     */

60     public ArrayList JavaDoc<RowObject> objects = new ArrayList JavaDoc<RowObject>();
61     /**
62       Number of space characters.
63     */

64     public int spaceCount = 0;
65     /**
66       Starting position of the row inside the buffer.
67     */

68     public long beginPosition;
69
70     public double height;
71     /**
72       Vertical location relative to the block frame.
73     */

74     public double y;
75     public double width;
76
77     Row(
78       long beginPosition,
79       double y
80       )
81     {
82       this.beginPosition = beginPosition;
83       this.y = y;
84     }
85   }
86
87   private static class RowObject
88   {
89     public RowObjectTypeEnum type;
90
91     public long beginPosition;
92
93     public double height;
94     public double width;
95
96     public int spaceCount;
97
98     RowObject(
99       RowObjectTypeEnum type,
100       long beginPosition,
101       double height,
102       double width,
103       int spaceCount
104       )
105     {
106       this.type = type;
107       this.beginPosition = beginPosition;
108       this.height = height;
109       this.width = width;
110       this.spaceCount = spaceCount;
111     }
112   }
113
114   private enum RowObjectTypeEnum
115   {
116     Text,
117     Image
118   }
119   // </classes>
120

121   // <dynamic>
122
/*
123     NOTE: In order to provide fine-grained alignment,
124     there are 2 postproduction state levels:
125       1- row level (see endRow());
126       2- block level (see end()).
127
128     NOTE: Graphics instructions' layout follows this scheme (XS-BNF syntax):
129       block = { beginLocalState translation parameters rows endLocalState }
130       beginLocalState { "q\r" }
131       translation = { "1 0 0 1 " number ' ' number "cm\r" }
132       parameters = { ... } // Graphics state parameters.
133       rows = { row* }
134       row = { object* }
135       object = { parameters beginLocalState translation content endLocalState }
136       content = { ... } // Text, image (and so on) showing operators.
137       endLocalState = { "Q\r" }
138     NOTE: all the graphics state parameters within a block are block-level ones,
139     i.e. they can't be represented inside row's or row object's local state, in order to
140     facilitate parameter reuse within the same block.
141   */

142   // <fields>
143
private IBuffer buffer;
144   private ContentBuilder context;
145
146   private AlignmentXEnum alignmentX;
147   private AlignmentYEnum alignmentY;
148   private boolean hyphenation;
149
150   /** Available area where to render the block contents inside. */
151   private Rectangle2D JavaDoc frame;
152   /** Actual area occupied by the block contents. */
153   private Rectangle2D.Double JavaDoc boundBox;
154
155   /** Starting position of the block inside the buffer. */
156   private long beginPosition;
157
158   private Row currentRow;
159   private boolean rowEnded;
160   // </fields>
161

162   // <constructors>
163
public BlockFilter(
164     ContentBuilder context
165     )
166   {
167     this.context = context;
168
169     buffer = context.getBuffer();
170   }
171   // </constructors>
172

173   // <interface>
174
// <public>
175
/**
176     Begins a content block.
177   */

178   public void begin(
179     Rectangle2D JavaDoc frame,
180     AlignmentXEnum alignmentX,
181     AlignmentYEnum alignmentY
182     )
183   {
184     this.frame = frame;
185     this.alignmentX = alignmentX;
186     this.alignmentY = alignmentY;
187
188     // Open the block local state!
189
/*
190       NOTE: This device allows a fine-grained control over the block representation.
191       It MUST be coupled with a closing statement on block end.
192     */

193     context.beginLocalState();
194
195     beginPosition = buffer.getLength();
196
197     boundBox = new Rectangle2D.Double JavaDoc(
198       frame.getX(),
199       frame.getY(),
200       frame.getWidth(),
201       0
202       );
203
204     beginRow();
205   }
206
207   /**
208     Ends the content block.
209   */

210   public void end(
211     )
212   {
213     // Is the block empty?
214
if(beginPosition < buffer.getLength())
215     {
216       // End last row!
217
endRow(true);
218
219       // Block translation.
220
buffer.insert(
221         (int)beginPosition,
222         "1 0 0 1 "
223           + PdfReal.toPdf(boundBox.x) + " " // Horizontal translation.
224
+ PdfReal.toPdf(-boundBox.y) // Vertical translation.
225
+ " cm\r"
226         );
227     }
228
229     // Close the block local state!
230
context.endLocalState();
231   }
232
233   /**
234     Gets the actual area occupied by the block contents.
235   */

236   public Rectangle2D JavaDoc getBoundBox(
237     )
238   {return boundBox;}
239
240   /**
241     Gets the inner buffer.
242   */

243   public IBuffer getBuffer(
244     )
245   {return buffer;}
246
247   /**
248     Gets the context of the content block builder.
249   */

250   public ContentBuilder getContext(
251     )
252   {return context;}
253
254   /**
255     Gets the available area where to render the block contents inside.
256   */

257   public Rectangle2D JavaDoc getFrame(
258     )
259   {return frame;}
260
261   /**
262     Gets whether the hyphenation algorithm has to be applied.
263   */

264   public boolean getHyphenation(
265     )
266   {return hyphenation;}
267
268   /**
269     Sets whether the hyphenation algorithm has to be applied.
270   */

271   public void setHyphenation(
272     boolean value
273     )
274   {hyphenation = value;}
275
276   /**
277     Ends current row.
278   */

279   public void showBreak(
280     )
281   {
282     endRow(true);
283     beginRow();
284   }
285
286   /**
287     Ends current row, specifying the offset of the next one.
288     <h3>Remarks</h3>
289     <p>This functionality allows higher-level features such as paragraph indentation and margin.</p>
290     @param offset Relative location of the next row.
291   */

292   public void showBreak(
293     Dimension2D JavaDoc offset
294     )
295   {
296     showBreak();
297
298     currentRow.y += offset.getHeight();
299     currentRow.width = offset.getWidth();
300   }
301
302   /**
303     Shows text.
304     @return Last shown character index.
305   */

306   public int showText(
307     String JavaDoc text
308     )
309   {
310     if(currentRow == null
311       || text.trim().length() == 0)
312       return 0;
313
314     ContentBuilder.GraphicsState state = context.getState();
315     Font font = state.getFont();
316     double fontSize = state.getFontSize();
317     TextFitter textFitter = new TextFitter(
318         text,
319         0,
320         font,
321         fontSize,
322         hyphenation
323         );
324     int textLength = text.length();
325     int index = 0;
326     double lineHeight = font.getLineHeight(fontSize);
327     while(true)
328     {
329       // Does the text height exceed current row's height?
330
if(lineHeight > currentRow.height)
331       {
332         // Does the text height exceed left block vertical space?
333
if(lineHeight > frame.getHeight() - currentRow.y) // Text exceeds.
334
{
335           // Terminate the current row!
336
endRow(false);
337
338           return index;
339         }
340         else // Text doesn't exceed.
341
{
342           // Adapt current row's height!
343
currentRow.height = lineHeight;
344         }
345       }
346
347       // Are we at the beginning of the current row?
348
if(currentRow.width == 0)
349       {
350         // Eliminating leading space...
351
while(text.charAt(index) == ' ')
352         {
353           if(index == textLength)
354             return index;
355
356           index++;
357         }
358       }
359
360       // Does the text fit?
361
if(textFitter.fit(
362         index,
363         frame.getWidth() - currentRow.width // Residual row width.
364
))
365       {
366         double textChunkWidth = textFitter.getFittedWidth();
367         String JavaDoc textChunk = textFitter.getFittedText();
368         Point2D JavaDoc textChunkLocation = new Point2D.Double JavaDoc(
369           currentRow.width,
370           currentRow.y
371           );
372
373         // Open the row object's local state!
374
/*
375           NOTE: This device allows a fine-grained control over the row object's representation.
376           It MUST be coupled with a closing statement on row object's end.
377         */

378         context.beginLocalState();
379
380         // Save postproduction info!
381
RowObject object = new RowObject(
382           RowObjectTypeEnum.Text,
383           buffer.getLength(),
384           lineHeight,
385           textChunkWidth,
386           countOccurrence(' ',textChunk)
387           );
388         currentRow.objects.add(object);
389         currentRow.spaceCount += object.spaceCount;
390
391         // Show the text chunk!
392
context.beginText();
393         context.showText(
394           textChunk,
395           textChunkLocation
396           );
397         context.endText();
398
399         // Close the row object's local state!
400
context.endLocalState();
401
402         // Update row width!
403
currentRow.width += textChunkWidth;
404
405         index = textFitter.getEndIndex();
406         // Did we reach the text end?
407
if(index == textLength)
408           break;
409       }
410
411       /*
412         NOTE: Stepping here means that text hasn't been fully rendered yet.
413       */

414       endRow(false);
415       beginRow();
416     }
417
418     return index;
419   }
420   // </public>
421

422   // <private>
423
/**
424     Begins a content row.
425   */

426   private void beginRow(
427     )
428   {
429     rowEnded = false;
430
431     currentRow = new Row(
432       buffer.getLength(),
433       boundBox.height
434       );
435   }
436
437   private int countOccurrence(
438     char value,
439     String JavaDoc text
440     )
441   {
442     int count = 0;
443     int fromIndex = 0;
444     do
445     {
446       int foundIndex = text.indexOf(value,fromIndex);
447       if(foundIndex == -1)
448         return count;
449
450       count++;
451
452       fromIndex = foundIndex + 1;
453     }
454     while(true);
455   }
456
457   /**
458     Ends the content row.
459     @param broken Indicates whether this is the end of a paragraph.
460   */

461   private void endRow(
462     boolean broken
463     )
464   {
465     if(rowEnded)
466       return;
467
468     rowEnded = true;
469
470     double objectXOffsets[] = new double[currentRow.objects.size()]; // Horizontal object displacements.
471
double wordSpacing = 0; // Exceeding space among words.
472
double rowXOffset = 0; // Horizontal row offset.
473

474     List JavaDoc<RowObject> objects = currentRow.objects;
475
476     // Horizontal alignment.
477
AlignmentXEnum alignmentX = this.alignmentX;
478     switch(alignmentX)
479     {
480       case Left:
481       {break;}
482       case Right:
483       {
484         rowXOffset = frame.getWidth() - currentRow.width;
485         break;
486       }
487       case Center:
488       {
489         rowXOffset = (frame.getWidth() - currentRow.width) / 2;
490         break;
491       }
492       case Justify:
493       {
494         // Are there NO spaces?
495
if(currentRow.spaceCount == 0
496           || broken) // NO spaces.
497
{
498           /* NOTE: This situation equals a simple left alignment. */
499           alignmentX = AlignmentXEnum.Left;
500         }
501         else // Spaces exist.
502
{
503           // Calculate the exceeding spacing among the words!
504
wordSpacing = (frame.getWidth() - currentRow.width) / currentRow.spaceCount;
505           // Define the horizontal offsets for justified alignment.
506
for(
507             int index = 1,
508               count = objects.size();
509             index < count;
510             index++
511             )
512           {
513             /*
514               NOTE: The offset represents the horizontal justification gap inserted
515               at the left side of each object.
516             */

517             objectXOffsets[index] = objectXOffsets[index - 1] + objects.get(index - 1).spaceCount * wordSpacing;
518           }
519         }
520         break;
521       }
522     }
523
524     // Vertical alignment and translation.
525
for(
526       int index = objects.size() - 1;
527       index >= 0;
528       index--
529       )
530     {
531       RowObject object = objects.get(index);
532
533       // Vertical alignment.
534
double objectYOffset = 0;
535       switch(object.type)
536       {
537         case Text:
538         {
539           objectYOffset = -(currentRow.height - object.height); // Linebase-anchored vertical alignment.
540
break;
541         }
542         case Image:
543         {
544           objectYOffset = -(currentRow.height - object.height) / 2; // Centered vertical alignment.
545
break;
546         }
547       }
548
549       // Translation.
550
/*
551         NOTE: Alignment is accomplished through a per-object translation.
552       */

553       buffer.insert(
554         (int)object.beginPosition,
555         "1 0 0 1 "
556           + PdfReal.toPdf(objectXOffsets[index] + rowXOffset) + " " // Horizontal alignment.
557
+ PdfReal.toPdf(objectYOffset) + " " // Vertical alignment.
558
+ "cm\r"
559         );
560     }
561
562     // Word spacing.
563
buffer.insert(
564       (int)currentRow.beginPosition,
565       PdfReal.toPdf(wordSpacing) + " Tw\r"
566       );
567
568     // Update the actual block height!
569
boundBox.height = currentRow.y + currentRow.height;
570
571     // Update the actual block vertical location!
572
double xOffset;
573     switch(alignmentY)
574     {
575       case Bottom:
576         xOffset = frame.getHeight() - boundBox.height;
577         break;
578       case Middle:
579         xOffset = (frame.getHeight() - boundBox.height) / 2;
580         break;
581       case Top:
582       default:
583         xOffset = 0;
584         break;
585     }
586     boundBox.y = frame.getY() + xOffset;
587
588     // Discard the current row!
589
currentRow = null;
590   }
591   // </private>
592
// </interface>
593
// </dynamic>
594
// </class>
595
}
Popular Tags