support for CAPTCHAs
[mir.git] / source / mircoders / pdf / PDFGenerator.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mircoders.pdf;
31
32 import com.lowagie.text.*;
33 import com.lowagie.text.pdf.*;
34 import mir.config.MirPropertiesConfiguration;
35 import mir.entity.EntityBrowser;
36 import mir.entity.adapter.EntityAdapter;
37 import mir.generator.GeneratorExc;
38 import mir.generator.GeneratorHelper;
39 import mir.log.LoggerWrapper;
40 import mir.misc.StringUtil;
41 import mir.util.DateTimeRoutines;
42 import mir.util.HTMLRoutines;
43 import mir.util.ParameterExpander;
44 import mircoders.entity.EntityContent;
45 import mircoders.entity.EntityImages;
46 import mircoders.global.MirGlobal;
47 import mircoders.localizer.MirLocalizerExc;
48 import mircoders.storage.DatabaseImages;
49 import multex.Failure;
50 import org.apache.oro.text.regex.*;
51
52 import java.io.ByteArrayOutputStream;
53 import java.io.IOException;
54 import java.net.MalformedURLException;
55 import java.util.*;
56 import java.util.List;
57
58 public class PDFGenerator {
59   public Document document;
60   public PdfWriter writer;
61   public PdfContentByte cb;
62   public String localImageDir;
63   public float currentYPosition;
64   public int currentPage;
65   public float pageWidth;
66   public float pageHeight;
67   public float verticalMargin;
68   public float horizontalMargin;
69   public float topEdge;
70   public float bottomEdge;
71   public float rightEdge;
72   public float leftEdge;
73
74   public int maxImageHeight;
75   public int maxImageWidth;
76   protected LoggerWrapper logger;
77
78   public int indexFontSize;
79   public int indexLineHeight;
80   public int indexFontFamily;
81
82   public float footerHeight;
83   public String footerText;
84   public int footerFontSize;
85   public int footerFontFamily;
86
87   public int metaHeight;
88   public int metaFontSize;
89   public int metaFontFamily;
90
91   public int descriptionLineHeight;
92   public int descriptionFontSize;
93   public int descriptionFontFamily;
94
95   public int contentLineHeight;
96   public int contentFontSize;
97   public int contentFontFamily;
98
99   public int sourceLineHeight;
100   public int sourceFontSize;
101   public int sourceFontFamily;
102
103   public int bigImageCaptionFontSize;
104   public int bigImageCaptionFontFamily;
105
106   protected MirPropertiesConfiguration configuration;
107   private static Pattern nobackslashr;
108   private static Pattern HxTag;
109   private static Pattern ListItemTag;
110   private static Pattern ListTag;
111   private static Pattern DivTag;
112   private static Pattern PTag;
113   private static Pattern PTagClose;
114   private static Pattern BRTag;
115   private static Pattern ATagAll;
116   private static Pattern noTags;
117   private static Pattern re1;
118   private static Pattern re2;
119   private static Pattern re3;
120
121   static {
122     Perl5Compiler compiler = new Perl5Compiler();
123
124     try {
125       Pattern nobackslashr = compiler.compile("\r");
126
127       HxTag = compiler.compile("</?h[1-6][^>]*>", Perl5Compiler.READ_ONLY_MASK +
128                                                      Perl5Compiler.CASE_INSENSITIVE_MASK );
129       ListItemTag = compiler.compile("<li[^>]*>", Perl5Compiler.CASE_INSENSITIVE_MASK);
130       ListTag = compiler.compile("<(u|o)l[^>]*>", Perl5Compiler.CASE_INSENSITIVE_MASK);
131       DivTag = compiler.compile("</?div[^>]*>", Perl5Compiler.CASE_INSENSITIVE_MASK);
132       PTag = compiler.compile("<(p|P)([:space:]+[^>]*)?>", Perl5Compiler.READ_ONLY_MASK);
133       PTagClose = compiler.compile("</(p|P)([:space:]+[^>]*)?>", Perl5Compiler.READ_ONLY_MASK);
134       BRTag = compiler.compile("<(br|BR)([:space:]+[^>]*)?>", Perl5Compiler.READ_ONLY_MASK);
135       ATagAll = compiler.compile("<a[^>]*href=(?:\"|\')([^#\"\'][^\'\"]+)(?:\"|\')[^>]*>(.*?)</a>",
136                                                      Perl5Compiler.READ_ONLY_MASK +
137                                                      Perl5Compiler.CASE_INSENSITIVE_MASK );
138       noTags = compiler.compile("<[^>]*>", Perl5Compiler.READ_ONLY_MASK);
139       re1 = compiler.compile("\r?\n\r?\n", Perl5Compiler.READ_ONLY_MASK);
140       re2 = compiler.compile("\r?\n", Perl5Compiler.READ_ONLY_MASK);
141       re3 = compiler.compile("BREAKHERE", Perl5Compiler.READ_ONLY_MASK);
142     }
143     catch (MalformedPatternException e) {
144       throw new RuntimeException(e.getMessage());
145     }
146   }
147
148
149   public PDFGenerator(ByteArrayOutputStream out) {
150     logger = new LoggerWrapper("PDFGenerator");
151     configuration = MirPropertiesConfiguration.instance();
152     localImageDir = configuration.getString("Producer.Image.Path");
153
154     try {
155       indexFontSize = Integer.parseInt(configuration.getString("PDF.Index.FontSize"));
156       indexLineHeight = Integer.parseInt(configuration.getString("PDF.Index.LineHeight"));
157       indexFontFamily = getFontByName(configuration.getString("PDF.Index.FontFamily"));
158
159       footerText = configuration.getString("PDF.Footer.String");
160       footerFontSize = Integer.parseInt(configuration.getString("PDF.Footer.FontSize"));
161       footerFontFamily = getFontByName(configuration.getString("PDF.Footer.FontFamily"));
162       footerHeight = Integer.parseInt(configuration.getString("PDF.Footer.Height"));
163
164       metaFontSize = Integer.parseInt(configuration.getString("PDF.Meta.FontSize"));
165       metaFontFamily = getFontByName(configuration.getString("PDF.Meta.FontFamily"));
166       metaHeight = Integer.parseInt(configuration.getString("PDF.Meta.Height"));
167
168       descriptionFontSize = Integer.parseInt(configuration.getString("PDF.Description.FontSize"));
169       descriptionLineHeight = Integer.parseInt(configuration.getString("PDF.Description.LineHeight"));
170       descriptionFontFamily = getFontByName(configuration.getString("PDF.Description.FontFamily"));
171
172       contentFontSize = Integer.parseInt(configuration.getString("PDF.Content.FontSize"));
173       contentLineHeight = Integer.parseInt(configuration.getString("PDF.Content.LineHeight"));
174       contentFontFamily = getFontByName(configuration.getString("PDF.Content.FontFamily"));
175
176       sourceFontSize = Integer.parseInt(configuration.getString("PDF.Source.FontSize"));
177       sourceLineHeight = Integer.parseInt(configuration.getString("PDF.Source.LineHeight"));
178       sourceFontFamily = getFontByName(configuration.getString("PDF.Source.FontFamily"));
179
180       bigImageCaptionFontSize = Integer.parseInt(configuration.getString("PDF.BigImageCaption.FontSize"));
181       bigImageCaptionFontFamily = getFontByName(configuration.getString("PDF.BigImageCaption.FontFamily"));
182
183     }
184     catch (NumberFormatException e) {
185       throw new Failure(e.getMessage(), e);
186     }
187
188     // step 1: make a document
189
190     String pageSize = configuration.getString("PDF.Pagesize");
191
192     if (pageSize.equals("LETTER")) {
193       document = new Document(PageSize.LETTER);
194       pageWidth = 612;
195       pageHeight = 792;
196     } else {
197       document = new Document(PageSize.A4);
198       pageWidth = 595;
199       pageHeight = 842;
200     }
201
202     maxImageHeight = 250;
203     maxImageWidth = 250;
204
205
206     verticalMargin = 20;
207     horizontalMargin = 20;
208
209
210     topEdge = pageHeight - verticalMargin;
211     bottomEdge = verticalMargin;
212     rightEdge = pageWidth - horizontalMargin;
213     leftEdge = horizontalMargin;
214
215     currentYPosition = topEdge;
216     currentPage = 1;
217
218     String headerText = configuration.getString("PDF.Title.String");
219
220     try {
221       writer = PdfWriter.getInstance(document, out);
222       cb = writer.getDirectContent();
223
224       document.open();
225       addHeader(headerText);
226     }
227     catch (DocumentException de) {
228       logger.error(de.getMessage());
229     }
230   }
231
232   public void stop() {
233     addFooter();
234     document.close();
235   }
236
237   public void addHeader(String headerText) {
238     int titleFontSize = Integer.parseInt(configuration.getString("PDF.Title.FontSize"));
239     int titleLineHeight = Integer.parseInt(configuration.getString("PDF.Title.LineHeight"));
240     String titleFontFamily = configuration.getString("PDF.Title.FontFamily");
241     String headerImage = configuration.getString("PDF.Header.Image");
242     int headerImageHeight = Integer.parseInt(configuration.getString("PDF.Header.ImageHeight"));
243
244     try {
245       if ((! headerImage.equals("")) && headerImageHeight != 0) {
246         PdfTemplate template = cb.createTemplate(rightEdge - horizontalMargin, headerImageHeight);
247
248         float toYPosition = currentYPosition - headerImageHeight;
249         Image theImage = Image.getInstance(headerImage);
250         theImage.setAbsolutePosition(0, 0);
251         //      theImage.scaleAbsolute(img_width,img_height);
252         template.addImage(theImage);
253
254
255         cb.addTemplate(template, leftEdge, toYPosition);
256         currentYPosition = toYPosition;
257       }
258       if (! headerText.equals("")) {
259         ColumnText ct = new ColumnText(cb);
260         //add a basic header
261         ct.addText(new Phrase(headerText, new Font(getFontByName(titleFontFamily), titleFontSize, Font.BOLD)));
262         float[] rightCol = {rightEdge, currentYPosition, rightEdge, currentYPosition - titleLineHeight};
263         float[] leftCol = {leftEdge, currentYPosition, leftEdge, currentYPosition - titleLineHeight};
264         ct.setColumns(leftCol, rightCol);
265         ct.setYLine(currentYPosition);
266         ct.setAlignment(Element.ALIGN_CENTER);
267         ct.go();
268
269         currentYPosition = currentYPosition - titleLineHeight;
270       }
271     }
272     catch (DocumentException de) {
273       logger.error(de.getMessage());
274     }
275     catch (MalformedURLException de) {
276       logger.error(de.getMessage());
277     }
278     catch (IOException de) {
279       logger.error(de.getMessage());
280     }
281   }
282
283   public void addIndexItem(EntityContent entityContent) {
284     try {
285
286       ColumnText ict = new ColumnText(cb);
287       String theTitle = entityContent.getFieldValue("title");
288       String theCreator = entityContent.getFieldValue("creator");
289       Phrase titleP = new Phrase(" - " + theTitle, new Font(indexFontFamily, indexFontSize, Font.BOLD));
290       Phrase creatorP = new Phrase(" :: " + theCreator, new Font(indexFontFamily, indexFontSize));
291       float toYPosition = currentYPosition - indexLineHeight;
292       float[] leftIndexCols = {leftEdge, currentYPosition, leftEdge, toYPosition};
293       float[] rightIndexCols = {rightEdge, currentYPosition, rightEdge, toYPosition};
294       ict.addText(titleP);
295       ict.addText(creatorP);
296       ict.setColumns(leftIndexCols, rightIndexCols);
297       ict.setYLine(currentYPosition);
298       ict.setAlignment(Element.ALIGN_LEFT);
299       ict.go();
300       currentYPosition = toYPosition;
301     }
302     catch (DocumentException de) {
303       logger.error(de.getMessage());
304     }
305
306
307   }
308
309   private int addTextLine(ColumnText ct, int lineHeight, int alignment, float left, float right) {
310     logger.debug("adding a line of text");
311     if (! enoughY(lineHeight)) {
312       newPage();
313     }
314     float toYPosition = currentYPosition - lineHeight;
315     float[] leftContentCols = {left, currentYPosition, left, toYPosition};
316     float[] rightContentCols = {right, currentYPosition, right, toYPosition};
317     ct.setColumns(leftContentCols, rightContentCols);
318     ct.setYLine(currentYPosition);
319     ct.setAlignment(alignment);
320     try {
321       int status = ct.go();
322       currentYPosition = toYPosition;
323       return status;
324     }
325     catch (DocumentException de) {
326       logger.error(de.getMessage());
327     }
328     return 0;
329   }
330
331   public void addLine() {
332     cb.setLineWidth(1f);
333     cb.moveTo(rightEdge, currentYPosition - 5);
334     cb.lineTo(leftEdge, currentYPosition - 5);
335     cb.stroke();
336     currentYPosition = currentYPosition - 10;
337
338   }
339
340   public void addFooter() {
341     try {
342       ColumnText fct = new ColumnText(cb);
343       cb.setLineWidth(1f);
344       cb.moveTo(rightEdge, bottomEdge + footerHeight - 5);
345       cb.lineTo(leftEdge, bottomEdge + footerHeight - 5);
346       cb.stroke();
347       float[] leftFooterCols = {leftEdge, bottomEdge + footerHeight - 1, leftEdge, bottomEdge};
348       float[] rightFooterCols = {rightEdge, bottomEdge + footerHeight - 1, rightEdge, bottomEdge};
349
350       Paragraph footerP = new Paragraph(footerText, new Font(footerFontFamily, footerFontSize));
351       fct.addText(footerP);
352
353       fct.setColumns(leftFooterCols, rightFooterCols);
354       fct.setYLine(bottomEdge + footerHeight - 1);
355       fct.setAlignment(Element.ALIGN_JUSTIFIED);
356       fct.go();
357
358       Paragraph numberP = new Paragraph((new Integer(currentPage)).toString(), new Font(footerFontFamily, footerFontSize, Font.BOLD));
359       fct.addText(numberP);
360       fct.setAlignment(Element.ALIGN_RIGHT);
361       fct.go();
362
363     }
364     catch (DocumentException de) {
365       logger.error(de.getMessage());
366     }
367
368
369   }
370
371   public void newPage() {
372     try {
373       //add a footer
374       addFooter();
375       document.newPage();
376       currentPage++;
377       currentYPosition = topEdge;
378     }
379     catch (DocumentException de) {
380       logger.error(de.getMessage());
381     }
382   }
383
384   public void addArticleSeparator() {
385     // make a line
386     if (! enoughY(10)) {
387       newPage();
388     }
389     cb.setLineWidth(1f);
390     cb.moveTo(rightEdge, currentYPosition - 5);
391     cb.lineTo(leftEdge, currentYPosition - 5);
392     cb.stroke();
393     currentYPosition = currentYPosition - 10;
394   }
395
396   public void addArticleMetaInfo(ColumnText ct, String theTitle, String theCreator, String theDate) {
397     //see if we have room for the author and title
398
399     if (! enoughY(metaHeight)) {
400       newPage();
401     }
402
403     Paragraph titleP = new Paragraph(theTitle + "\n", new Font(metaFontFamily, metaFontSize, Font.BOLD));
404     Paragraph whowhenP = new Paragraph(theCreator + "  " + theDate, new Font(metaFontFamily, metaFontSize));
405     ct.addText(titleP);
406     ct.addText(whowhenP);
407
408     ct.setYLine(currentYPosition);
409     ct.setAlignment(Element.ALIGN_LEFT);
410
411     float toYPosition = currentYPosition - metaHeight;
412     float[] leftHeadCols = {leftEdge, currentYPosition, leftEdge, toYPosition};
413     float[] rightHeadCols = {rightEdge, currentYPosition, rightEdge, toYPosition};
414
415     ct.setColumns(leftHeadCols, rightHeadCols);
416     try {
417       ct.go();
418       currentYPosition = toYPosition;
419     }
420     catch (DocumentException de) {
421       logger.error(de.getMessage());
422     }
423
424   }
425
426   public void addArticleDescription(ColumnText ct, String theDescription) {
427     // Now we add the description, one line at a time, the ct should be empty at this point
428
429
430     Paragraph descP = new Paragraph(theDescription, new Font(descriptionFontFamily, descriptionFontSize, Font.BOLD));
431     ct.addText(descP);
432
433     // every article has a description, so we can assume that:
434     int status = ColumnText.NO_MORE_COLUMN;
435
436     int brake = 1000;
437     while ((status & ColumnText.NO_MORE_TEXT) == 0 && brake > 0) {
438       //there is still text left in the description.
439       status = addTextLine(ct, descriptionLineHeight, Element.ALIGN_JUSTIFIED, leftEdge, rightEdge);
440       brake--;
441     }
442     if (brake == 0)
443       logger.error("runaway description...try increasing the line height or decreasing the font size");
444   }
445
446   public void addArticleContent(ColumnText ct, String theContent, Iterator images) {
447     //let's go ahead and add in all the body text
448     Paragraph contentP = new Paragraph(theContent, new Font(contentFontFamily, contentFontSize));
449     ct.addText(contentP);
450
451     int contentLinesBeforeImages = 3;
452     //and assume we have at least one line of text in the content
453     int status = ColumnText.NO_MORE_COLUMN;
454
455     // let's add a little bit of text, like a couple of lines
456     int x = 0;
457     while (((status & ColumnText.NO_MORE_TEXT) == 0) && x < contentLinesBeforeImages) {
458       status = addTextLine(ct, contentLineHeight, Element.ALIGN_JUSTIFIED, leftEdge, rightEdge);
459       x++;
460     }
461
462     boolean addImageOnLeft = true; //we will alternate within articles
463     while (images.hasNext()) {
464
465       EntityImages currentImage = (EntityImages) images.next();
466       float img_width = (new Float(currentImage.getFieldValue("img_width"))).floatValue();
467       float img_height = (new Float(currentImage.getFieldValue("img_height"))).floatValue();
468       if (img_height > maxImageHeight) {
469         img_width = (new Float((new Float(img_width * (maxImageHeight / img_height))).intValue())).floatValue();
470         img_height = maxImageHeight;
471       }
472       if (img_width > maxImageWidth) {
473         img_height = (new Float((new Float(img_height * (maxImageWidth / img_width))).intValue())).floatValue();
474         img_width = maxImageWidth;
475       }
476
477       String img_title = currentImage.getFieldValue("title");
478       String img_path = currentImage.getFieldValue("publish_path");
479
480       if ((status & ColumnText.NO_MORE_TEXT) == 0) {
481         // there is still text, so add an image which will have text wrapped around it, then add the text which
482         // will be on the left or the right of the image
483
484         float templateMinimumHeight = img_height + 20;
485         float templateWidth = img_width + 10;
486         float templateHeight = templateMinimumHeight + contentLineHeight - (templateMinimumHeight % contentLineHeight);
487
488         PdfTemplate template = cb.createTemplate(templateWidth, templateHeight);
489
490         //here we need a page check
491         if (! enoughY((new Float(templateHeight)).intValue())) {
492           //ok, well just fill text to the bottom then
493           float toYPosition = bottomEdge + footerHeight;
494           float[] leftBottomCols = {leftEdge, currentYPosition, leftEdge, toYPosition};
495           float[] rightBottomCols = {rightEdge, currentYPosition, rightEdge, toYPosition};
496           ct.setColumns(leftBottomCols, rightBottomCols);
497           ct.setYLine(currentYPosition);
498           ct.setAlignment(Element.ALIGN_JUSTIFIED);
499           try {
500             status = ct.go();
501           }
502           catch (DocumentException de) {
503             logger.error(de.getMessage());
504           }
505           newPage();
506         }
507
508         float toYPosition = currentYPosition - templateHeight;
509
510         try {
511           Image theImage = Image.getInstance(localImageDir + img_path);
512           theImage.scaleAbsolute(img_width, img_height);
513           theImage.setAbsolutePosition(5, 13);
514
515           template.addImage(theImage);
516           template.beginText();
517           BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
518           template.setFontAndSize(bf, 8);
519           template.setTextMatrix(5, 3);
520           template.showText(img_title);
521           template.endText();
522         }
523         catch (BadElementException de) {
524           logger.error(de.getMessage());
525         }
526         catch (DocumentException de) {
527           logger.error(de.getMessage());
528         }
529         catch (MalformedURLException de) {
530           logger.error(de.getMessage());
531         }
532         catch (IOException de) {
533           logger.error(de.getMessage());
534         }
535
536
537         float leftImageTextEdge = leftEdge;
538         float rightImageTextEdge = rightEdge;
539
540
541         if (addImageOnLeft) {
542           cb.addTemplate(template, leftEdge, toYPosition);
543           leftImageTextEdge = leftEdge + templateWidth + 5;
544           addImageOnLeft = false;
545         } else {
546           cb.addTemplate(template, rightEdge - templateWidth, toYPosition);
547           rightImageTextEdge = rightEdge - templateWidth - 5;
548           addImageOnLeft = true;
549         }
550
551         logger.debug("adding template at " + leftEdge + "," + toYPosition);
552
553         //and fill some text while we are at it
554
555         float[] leftBottomCols = {leftImageTextEdge, currentYPosition, leftImageTextEdge, toYPosition};
556         float[] rightBottomCols = {rightImageTextEdge, currentYPosition, rightImageTextEdge, toYPosition};
557         ct.setColumns(leftBottomCols, rightBottomCols);
558         ct.setYLine(currentYPosition);
559         ct.setAlignment(Element.ALIGN_JUSTIFIED);
560         try {
561           status = ct.go();
562           currentYPosition = toYPosition;
563         }
564         catch (DocumentException de) {
565           logger.error(de.getMessage());
566         }
567
568
569       } else {
570         //add an image on the left with a big caption to the right
571         currentYPosition = currentYPosition - 10;
572         float templateMinimumHeight = img_height;
573         float templateWidth = img_width;
574         float templateHeight = templateMinimumHeight + contentLineHeight - (templateMinimumHeight % contentLineHeight);
575         PdfTemplate template = cb.createTemplate(templateWidth, templateHeight);
576         if (! enoughY((new Float(templateHeight)).intValue())) {
577           newPage();
578         }
579         float toYPosition = currentYPosition - templateHeight;
580         try {
581           Image theImage = Image.getInstance(localImageDir + img_path);
582           theImage.setAbsolutePosition(0, 13);
583           theImage.scaleAbsolute(img_width, img_height);
584           template.addImage(theImage);
585         }
586         catch (BadElementException de) {
587           logger.error(de.getMessage());
588         }
589         catch (DocumentException de) {
590           logger.error(de.getMessage());
591         }
592         catch (MalformedURLException de) {
593           logger.error(de.getMessage());
594         }
595         catch (IOException de) {
596           logger.error(de.getMessage());
597         }
598
599         cb.addTemplate(template, leftEdge, toYPosition);
600
601         // add a big caption
602         ColumnText cct = new ColumnText(cb);
603         float[] leftCaptionCols = {leftEdge + templateWidth + 5, currentYPosition - 5, leftEdge + templateWidth + 5, toYPosition};
604         float[] rightCaptionCols = {rightEdge, currentYPosition - 5, rightEdge, toYPosition};
605
606         Paragraph captionP = new Paragraph(img_title, new Font(bigImageCaptionFontFamily, bigImageCaptionFontSize, Font.BOLD));
607         cct.addText(captionP);
608         cct.setColumns(leftCaptionCols, rightCaptionCols);
609         cct.setYLine(currentYPosition - 5);
610         cct.setAlignment(Element.ALIGN_LEFT);
611         try {
612           cct.go();
613           currentYPosition = toYPosition;
614         }
615         catch (DocumentException de) {
616           logger.error(de.getMessage());
617         }
618       }
619     }
620
621     //add the rest of the text which might be left
622     int brake = 10000;
623     while ((status & ColumnText.NO_MORE_TEXT) == 0 && brake > 0) {
624       status = addTextLine(ct, contentLineHeight, Element.ALIGN_JUSTIFIED, leftEdge, rightEdge);
625       brake --;
626     }
627     if (brake == 0)
628       logger.error("runaway content....try decreasing font size or increasing line height");
629   }
630
631   private void addArticleSource(ColumnText ct, String theSource) {
632     Paragraph sourceP = new Paragraph(theSource, new Font(sourceFontFamily, sourceFontSize, Font.BOLD));
633     ct.addText(sourceP);
634     addTextLine(ct, sourceLineHeight, Element.ALIGN_RIGHT, leftEdge, rightEdge);
635   }
636
637
638   private boolean enoughY(int heightOfBlockToAdd) {
639     return (currentYPosition - heightOfBlockToAdd - footerHeight) >= bottomEdge;
640   }
641
642
643   public void add(EntityContent anArticle, Locale aLocale) throws GeneratorExc, MirLocalizerExc, Exception {
644     logger.debug("adding a content Entity");
645
646     /*
647      * initialize
648      * separate
649      * meta info
650      * description
651      * content
652      * --image with text
653      * --normal text
654      * --image without text
655      * source
656     */
657
658     List extraTables = new ArrayList();
659     extraTables.add("content_x_media cxm");
660     Iterator images = new EntityBrowser(
661         DatabaseImages.getInstance(), "i", extraTables,
662         "cxm.content_id=" + anArticle.getId() + "and cxm.media_id=i.id",
663         "i.id desc", 30, -1, 0);
664
665     String isHTML = anArticle.getFieldValue("is_html");
666     String theTitle = anArticle.getFieldValue("title");
667     String theCreator = anArticle.getFieldValue("creator");
668     String theDate = DateTimeRoutines.advancedDateFormat(
669         configuration.getString("RDF.Meta.DateFormat"),
670         StringUtil.convertMirInternalDateToDate(anArticle.getFieldValue("webdb_create")),
671         configuration.getString("Mir.DefaultTimezone"));
672
673
674     String theDescriptionRaw = anArticle.getFieldValue("description");
675     String theContentRaw = anArticle.getFieldValue("content_data");
676
677     MirPropertiesConfiguration configuration = MirPropertiesConfiguration.instance();
678     Map variables = GeneratorHelper.makeBasicGenerationData(new Locale[] {
679             aLocale ,new Locale(configuration.getString("Mir.Admin.FallbackLanguage", "en"), "")},
680             "bundles.open","bundles.open");
681
682     EntityAdapter article =
683             MirGlobal.localizer().dataModel().adapterModel().makeEntityAdapter("content", anArticle);
684     variables.put("article", article);
685     String source = ParameterExpander.expandExpression(
686             variables,
687             configuration.getString("PDF.Source")
688     );
689     String theContent = "";
690     String theDescription = "";
691
692     if (isHTML.equals("1")) {
693         theContent =
694               Util.substitute(new Perl5Matcher(), nobackslashr, new Perl5Substitution(""), theContentRaw, Util.SUBSTITUTE_ALL);
695         theDescription =
696                 Util.substitute(new Perl5Matcher(), nobackslashr, new Perl5Substitution(""), theDescriptionRaw, Util.SUBSTITUTE_ALL);
697
698         theContent = Util.substitute(new Perl5Matcher(), HxTag, new Perl5Substitution("\n\n"), theContent, Util.SUBSTITUTE_ALL);
699         theDescription = Util.substitute(new Perl5Matcher(), HxTag, new Perl5Substitution("\n\n"), theDescription, Util.SUBSTITUTE_ALL);
700         theContent  = Util.substitute(new Perl5Matcher(), ListItemTag, new Perl5Substitution("\n * "), theContent, Util.SUBSTITUTE_ALL);
701         theDescription  = Util.substitute(new Perl5Matcher(), ListItemTag, new Perl5Substitution("\n * "), theDescription, Util.SUBSTITUTE_ALL);
702
703         theContent  = Util.substitute(new Perl5Matcher(), ListTag, new Perl5Substitution("\n"), theContent, Util.SUBSTITUTE_ALL);
704         theDescription  = Util.substitute(new Perl5Matcher(), ListTag, new Perl5Substitution("\n"), theDescription, Util.SUBSTITUTE_ALL);
705
706         theContent = Util.substitute(new Perl5Matcher(), DivTag, new Perl5Substitution("\n"), theContent, Util.SUBSTITUTE_ALL);
707         theDescription = Util.substitute(new Perl5Matcher(), DivTag, new Perl5Substitution("\n"), theDescription, Util.SUBSTITUTE_ALL);
708
709         theContent = Util.substitute(new Perl5Matcher(), PTag, new Perl5Substitution("\n    "), theContent, Util.SUBSTITUTE_ALL);
710         theDescription = Util.substitute(new Perl5Matcher(), PTag, new Perl5Substitution("\n    "), theDescription, Util.SUBSTITUTE_ALL);
711
712         theContent = Util.substitute(new Perl5Matcher(), PTagClose, new Perl5Substitution("\n"), theContent, Util.SUBSTITUTE_ALL);
713         theDescription = Util.substitute(new Perl5Matcher(), PTagClose, new Perl5Substitution("\n"), theDescription, Util.SUBSTITUTE_ALL);
714
715         theContent = Util.substitute(new Perl5Matcher(), BRTag, new Perl5Substitution("\n"), theContent, Util.SUBSTITUTE_ALL);
716         theDescription = Util.substitute(new Perl5Matcher(), BRTag, new Perl5Substitution("\n"), theDescription, Util.SUBSTITUTE_ALL);
717
718
719         Perl5Matcher matcher = new Perl5Matcher();
720         PatternMatcherInput input = new PatternMatcherInput(theContent);
721         String theContentCopy = theContent;
722         while (matcher.contains(input, ATagAll)) {
723           String atagString = matcher.getMatch().group(0);
724           String atagStringHref = matcher.getMatch().group(1);
725           String atagStringText = matcher.getMatch().group(2);
726           int begin = matcher.getMatch().beginOffset(0);
727           theContentCopy = 
728                   theContentCopy.substring(0, begin) + atagStringText +
729                           " [" + atagStringHref + "] " + theContentCopy.substring(begin + atagString.length());
730         }
731         theContent = theContentCopy;
732
733
734         input = new PatternMatcherInput(theDescription);
735
736         String theDescriptionCopy = theDescription;
737         while (matcher.contains(input, ATagAll)) {
738           String atagString = matcher.getMatch().group(0);
739           String atagStringHref = matcher.getMatch().group(1);
740           String atagStringText = matcher.getMatch().group(2);
741           int begin = matcher.getMatch().beginOffset(0);
742           theDescriptionCopy = theDescriptionCopy.substring(0, begin) + atagStringText + " [" + atagStringHref + "] " +
743                   theDescriptionCopy.substring(begin + atagString.length());
744         }
745         theDescription = theDescriptionCopy;
746
747
748         theContent = Util.substitute(new Perl5Matcher(), noTags, new Perl5Substitution(" "), theContent, Util.SUBSTITUTE_ALL);
749         theDescription = Util.substitute(new Perl5Matcher(), noTags, new Perl5Substitution(" "), theDescription, Util.SUBSTITUTE_ALL);
750
751         theContent = HTMLRoutines.resolveHTMLEntites(theContent);
752         theDescription = HTMLRoutines.resolveHTMLEntites(theDescription);
753
754         String theDescription1 = Util.substitute(new Perl5Matcher(), re1, new Perl5Substitution("BREAKHERE"), theDescription, Util.SUBSTITUTE_ALL);
755
756         String theDescription2 = Util.substitute(new Perl5Matcher(), re2, new Perl5Substitution(" "), theDescription1, Util.SUBSTITUTE_ALL);
757
758         theDescription = Util.substitute(new Perl5Matcher(), re3, new Perl5Substitution("\n    "), theDescription2, Util.SUBSTITUTE_ALL);
759     }
760     else {
761       String theContent1 = Util.substitute(new Perl5Matcher(), re1,
762               new Perl5Substitution("BREAKHERE"), theContentRaw, Util.SUBSTITUTE_ALL);
763       String theDescription1 = Util.substitute(new Perl5Matcher(), re1,
764               new Perl5Substitution("BREAKHERE"), theDescriptionRaw, Util.SUBSTITUTE_ALL);
765
766       String theContent2 = Util.substitute(new Perl5Matcher(), re2,
767               new Perl5Substitution(" "), theContent1, Util.SUBSTITUTE_ALL);
768       String theDescription2 = Util.substitute(new Perl5Matcher(), re2,
769               new Perl5Substitution(" "), theDescription1, Util.SUBSTITUTE_ALL);
770
771       theContent = "    " + Util.substitute(new Perl5Matcher(), re3,
772               new Perl5Substitution("\n    "), theContent2, Util.SUBSTITUTE_ALL);
773       theDescription = Util.substitute(new Perl5Matcher(), re3,
774               new Perl5Substitution("\n    "), theDescription2, Util.SUBSTITUTE_ALL);
775     }
776
777     addArticleSeparator();
778
779     ColumnText ct = new ColumnText(cb);
780
781     addArticleMetaInfo(ct, theTitle, theCreator, theDate);
782     addArticleDescription(ct, theDescription);
783     addArticleContent(ct, theContent, images);
784     addArticleSource(ct, source);
785   }
786
787   public int getFontByName(String fontName) {
788     int theFont = 0;
789     if ("helvetica".equalsIgnoreCase(fontName)) {
790       theFont = Font.HELVETICA;
791     }
792     else if ("courier".equalsIgnoreCase(fontName)) {
793       theFont = Font.COURIER;
794     }
795     else if ("times".equalsIgnoreCase(fontName)) {
796       theFont = Font.TIMES_ROMAN;
797     }
798     else {
799       logger.error("using helvetica because I can't get font for name: " + fontName);
800       theFont = Font.HELVETICA;
801     }
802
803     return theFont;
804   }
805 }