001/*
002 * $Id: FactoryProperties.java 4610 2010-11-02 17:28:50Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.html.simpleparser;
045
046import java.io.File;
047import java.io.IOException;
048import java.util.HashMap;
049import java.util.Map;
050import java.util.StringTokenizer;
051
052import com.itextpdf.text.BaseColor;
053import com.itextpdf.text.Chunk;
054import com.itextpdf.text.DocListener;
055import com.itextpdf.text.DocumentException;
056import com.itextpdf.text.Font;
057import com.itextpdf.text.FontFactory;
058import com.itextpdf.text.FontProvider;
059import com.itextpdf.text.Image;
060import com.itextpdf.text.List;
061import com.itextpdf.text.ListItem;
062import com.itextpdf.text.Paragraph;
063import com.itextpdf.text.html.HtmlTags;
064import com.itextpdf.text.html.HtmlUtilities;
065import com.itextpdf.text.pdf.BaseFont;
066import com.itextpdf.text.pdf.HyphenationAuto;
067import com.itextpdf.text.pdf.HyphenationEvent;
068import com.itextpdf.text.pdf.draw.LineSeparator;
069/**
070 * Factory that produces iText Element objects,
071 * based on tags and their properties.
072 * @author blowagie
073 * @author psoares
074 * @since 5.0.6 (renamed)
075 */
076public class ElementFactory {
077
078
079
080        /**
081         * The font provider that will be used to fetch fonts.
082         * @since       iText 5.0       This used to be a FontFactoryImp
083         */
084        private FontProvider provider = FontFactory.getFontImp();
085
086        /**
087         * Creates a new instance of FactoryProperties.
088         */
089        public ElementFactory() {
090        }
091
092        /**
093         * Setter for the font provider
094         * @param provider
095         * @since       5.0.6 renamed from setFontImp
096         */
097        public void setFontProvider(final FontProvider provider) {
098                this.provider = provider;
099        }
100
101        /**
102         * Getter for the font provider
103         * @return provider
104         * @since 5.0.6 renamed from getFontImp
105         */
106        public FontProvider getFontProvider() {
107                return provider;
108        }
109
110        /**
111         * Creates a Font object based on a chain of properties.
112         * @param       chain   chain of properties
113         * @return      an iText Font object
114         */
115        public Font getFont(final ChainedProperties chain) {
116
117                // [1] font name
118
119                String face = chain.getProperty(HtmlTags.FACE);
120                // try again, under the CSS key.
121                //ISSUE: If both are present, we always go with face, even if font-family was
122                //  defined more recently in our ChainedProperties.  One solution would go like this:
123                //    Map all our supported style attributes to the 'normal' tag name, so we could
124                //    look everything up under that one tag, retrieving the most current value.
125                if (face == null || face.trim().length() == 0) {
126                        face = chain.getProperty(HtmlTags.FONTFAMILY);
127                }
128                // if the font consists of a comma separated list,
129                // take the first font that is registered
130                if (face != null) {
131                        StringTokenizer tok = new StringTokenizer(face, ",");
132                        while (tok.hasMoreTokens()) {
133                                face = tok.nextToken().trim();
134                                if (face.startsWith("\""))
135                                        face = face.substring(1);
136                                if (face.endsWith("\""))
137                                        face = face.substring(0, face.length() - 1);
138                                if (provider.isRegistered(face))
139                                        break;
140                        }
141                }
142
143                // [2] encoding
144                String encoding = chain.getProperty(HtmlTags.ENCODING);
145                if (encoding == null)
146                        encoding = BaseFont.WINANSI;
147
148                // [3] embedded
149
150                // [4] font size
151                String value = chain.getProperty(HtmlTags.SIZE);
152                float size = 12;
153                if (value != null)
154                        size = Float.parseFloat(value);
155
156                // [5] font style
157                int style = 0;
158
159                // text-decoration
160                String decoration = chain.getProperty(HtmlTags.TEXTDECORATION);
161                if (decoration != null && decoration.trim().length() != 0) {
162                  if (HtmlTags.UNDERLINE.equals(decoration)) {
163                    style |= Font.UNDERLINE;
164                  } else if (HtmlTags.LINETHROUGH.equals(decoration)) {
165                    style |= Font.STRIKETHRU;
166                  }
167                }
168                // italic
169                if (chain.hasProperty(HtmlTags.I))
170                        style |= Font.ITALIC;
171                // bold
172                if (chain.hasProperty(HtmlTags.B))
173                        style |= Font.BOLD;
174                // underline
175                if (chain.hasProperty(HtmlTags.U))
176                        style |= Font.UNDERLINE;
177                // strikethru
178                if (chain.hasProperty(HtmlTags.S))
179                        style |= Font.STRIKETHRU;
180
181                // [6] Color
182                BaseColor color = HtmlUtilities.decodeColor(chain.getProperty(HtmlTags.COLOR));
183
184                // Get the font object from the provider
185                return provider.getFont(face, encoding, true, size, style, color);
186        }
187
188
189        /**
190         * Creates an iText Chunk
191         * @param content the content of the Chunk
192         * @param chain the hierarchy chain
193         * @return a Chunk
194         */
195        public Chunk createChunk(final String content, final ChainedProperties chain) {
196                Font font = getFont(chain);
197                Chunk ck = new Chunk(content, font);
198                if (chain.hasProperty(HtmlTags.SUB))
199                        ck.setTextRise(-font.getSize() / 2);
200                else if (chain.hasProperty(HtmlTags.SUP))
201                        ck.setTextRise(font.getSize() / 2);
202                ck.setHyphenation(getHyphenation(chain));
203                return ck;
204        }
205
206        /**
207         * Creates an iText Paragraph object using the properties
208         * of the different tags and properties in the hierarchy chain.
209         * @param       chain   the hierarchy chain
210         * @return      a Paragraph without any content
211         */
212        public Paragraph createParagraph(final ChainedProperties chain) {
213                Paragraph paragraph = new Paragraph();
214                updateElement(paragraph, chain);
215                return paragraph;
216        }
217
218        /**
219         * Creates an iText Paragraph object using the properties
220         * of the different tags and properties in the hierarchy chain.
221         * @param       chain   the hierarchy chain
222         * @return      a ListItem without any content
223         */
224        public ListItem createListItem(final ChainedProperties chain) {
225                ListItem item = new ListItem();
226                updateElement(item, chain);
227                return item;
228        }
229
230        /**
231         * Method that does the actual Element creating for
232         * the createParagraph and createListItem method.
233         * @param paragraph
234         * @param chain
235         */
236        protected void updateElement(final Paragraph paragraph, final ChainedProperties chain) {
237                // Alignment
238                String value = chain.getProperty(HtmlTags.ALIGN);
239                paragraph.setAlignment(HtmlUtilities.alignmentValue(value));
240                // hyphenation
241                paragraph.setHyphenation(getHyphenation(chain));
242                // leading
243                setParagraphLeading(paragraph, chain.getProperty(HtmlTags.LEADING));
244                // spacing before
245                value = chain.getProperty(HtmlTags.AFTER);
246                if (value != null) {
247                        try {
248                                paragraph.setSpacingBefore(Float.parseFloat(value));
249                        } catch (Exception e) {
250                        }
251                }
252                // spacing after
253                value = chain.getProperty(HtmlTags.AFTER);
254                if (value != null) {
255                        try {
256                                paragraph.setSpacingAfter(Float.parseFloat(value));
257                        } catch (Exception e) {
258                        }
259                }
260                // extra paragraph space
261                value = chain.getProperty(HtmlTags.EXTRAPARASPACE);
262                if (value != null) {
263                        try {
264                                paragraph.setExtraParagraphSpace(Float.parseFloat(value));
265                        } catch (Exception e) {
266                        }
267                }
268                // indentation
269                value = chain.getProperty(HtmlTags.INDENT);
270                if (value != null) {
271                        try {
272                                paragraph.setIndentationLeft(Float.parseFloat(value));
273                        } catch (Exception e) {
274                        }
275                }
276        }
277
278        /**
279         * Sets the leading of a Paragraph object.
280         * @param       paragraph       the Paragraph for which we set the leading
281         * @param       leading         the String value of the leading
282         */
283        protected static void setParagraphLeading(final Paragraph paragraph, final String leading) {
284                // default leading
285                if (leading == null) {
286                        paragraph.setLeading(0, 1.5f);
287                        return;
288                }
289                try {
290                        StringTokenizer tk = new StringTokenizer(leading, " ,");
291                        // absolute leading
292                        String v = tk.nextToken();
293                        float v1 = Float.parseFloat(v);
294                        if (!tk.hasMoreTokens()) {
295                                paragraph.setLeading(v1, 0);
296                                return;
297                        }
298                        // relative leading
299                        v = tk.nextToken();
300                        float v2 = Float.parseFloat(v);
301                        paragraph.setLeading(v1, v2);
302                } catch (Exception e) {
303                        // default leading
304                        paragraph.setLeading(0, 1.5f);
305                }
306        }
307
308
309        /**
310         * Gets a HyphenationEvent based on the hyphenation entry in
311         * the hierarchy chain.
312         * @param       chain   the hierarchy chain
313         * @return      a HyphenationEvent
314         * @since       2.1.2
315         */
316        public HyphenationEvent getHyphenation(final ChainedProperties chain) {
317                String value = chain.getProperty(HtmlTags.HYPHENATION);
318                // no hyphenation defined
319                if (value == null || value.length() == 0) {
320                        return null;
321                }
322                // language code only
323                int pos = value.indexOf('_');
324                if (pos == -1) {
325                        return new HyphenationAuto(value, null, 2, 2);
326                }
327                // language and country code
328                String lang = value.substring(0, pos);
329                String country = value.substring(pos + 1);
330                // no leftMin or rightMin
331                pos = country.indexOf(',');
332                if (pos == -1) {
333                        return new HyphenationAuto(lang, country, 2, 2);
334                }
335                // leftMin and rightMin value
336                int leftMin;
337                int rightMin = 2;
338                value = country.substring(pos + 1);
339                country = country.substring(0, pos);
340                pos = value.indexOf(',');
341                if (pos == -1) {
342                        leftMin = Integer.parseInt(value);
343                } else {
344                        leftMin = Integer.parseInt(value.substring(0, pos));
345                        rightMin = Integer.parseInt(value.substring(pos + 1));
346                }
347                return new HyphenationAuto(lang, country, leftMin, rightMin);
348        }
349
350        /**
351         * Creates a LineSeparator.
352         * @param attrs the attributes
353         * @param offset
354         * @return a LineSeparator
355         * @since 5.0.6
356         */
357        public LineSeparator createLineSeparator(final Map<String, String> attrs, final float offset) {
358                // line thickness
359                float lineWidth = 1;
360                String size = attrs.get(HtmlTags.SIZE);
361                if (size != null) {
362                        float tmpSize = HtmlUtilities.parseLength(size, HtmlUtilities.DEFAULT_FONT_SIZE);
363                        if (tmpSize > 0)
364                                lineWidth = tmpSize;
365                }
366                // width percentage
367                String width = attrs.get(HtmlTags.WIDTH);
368                float percentage = 100;
369                if (width != null) {
370                        float tmpWidth = HtmlUtilities.parseLength(width, HtmlUtilities.DEFAULT_FONT_SIZE);
371                        if (tmpWidth > 0) percentage = tmpWidth;
372                        if (!width.endsWith("%"))
373                                percentage = 100; // Treat a pixel width as 100% for now.
374                }
375                // line color
376                BaseColor lineColor = null;
377                // alignment
378                int align = HtmlUtilities.alignmentValue(attrs.get(HtmlTags.ALIGN));
379                return new LineSeparator(lineWidth, percentage, lineColor, align, offset);
380        }
381
382        /**
383         * @param src
384         * @param attrs
385         * @param chain
386         * @param document
387         * @param img_provider
388         * @param img_store
389         * @param img_baseurl
390         * @return the Image
391         * @throws DocumentException
392         * @throws IOException
393         */
394        public Image createImage(
395                        String src,
396                        final Map<String, String> attrs,
397                        final ChainedProperties chain,
398                        final DocListener document,
399                        final ImageProvider img_provider,
400                        final HashMap<String, Image> img_store,
401                        final String img_baseurl) throws DocumentException, IOException {
402                Image img = null;
403                // getting the image using an image provider
404                if (img_provider != null)
405                        img = img_provider.getImage(src, attrs, chain, document);
406                // getting the image from an image store
407                if (img == null && img_store != null) {
408                        Image tim = img_store.get(src);
409                        if (tim != null)
410                                img = Image.getInstance(tim);
411                }
412                if (img != null)
413                        return img;
414                // introducing a base url
415                // relative src references only
416                if (!src.startsWith("http") && img_baseurl != null) {
417                        src = img_baseurl + src;
418                }
419                else if (img == null && !src.startsWith("http")) {
420                        String path = chain.getProperty(HtmlTags.IMAGEPATH);
421                        if (path == null)
422                                path = "";
423                        src = new File(path, src).getPath();
424                }
425                img = Image.getInstance(src);
426                if (img == null)
427                        return null;
428
429                float actualFontSize = HtmlUtilities.parseLength(
430                        chain.getProperty(HtmlTags.SIZE),
431                        HtmlUtilities.DEFAULT_FONT_SIZE);
432                if (actualFontSize <= 0f)
433                        actualFontSize = HtmlUtilities.DEFAULT_FONT_SIZE;
434                String width = attrs.get(HtmlTags.WIDTH);
435                float widthInPoints = HtmlUtilities.parseLength(width, actualFontSize);
436                String height = attrs.get(HtmlTags.HEIGHT);
437                float heightInPoints = HtmlUtilities.parseLength(height, actualFontSize);
438                if (widthInPoints > 0 && heightInPoints > 0) {
439                        img.scaleAbsolute(widthInPoints, heightInPoints);
440                } else if (widthInPoints > 0) {
441                        heightInPoints = img.getHeight() * widthInPoints
442                                        / img.getWidth();
443                        img.scaleAbsolute(widthInPoints, heightInPoints);
444                } else if (heightInPoints > 0) {
445                        widthInPoints = img.getWidth() * heightInPoints
446                                        / img.getHeight();
447                        img.scaleAbsolute(widthInPoints, heightInPoints);
448                }
449
450                String before = chain.getProperty(HtmlTags.BEFORE);
451                if (before != null)
452                        img.setSpacingBefore(Float.parseFloat(before));
453                String after = chain.getProperty(HtmlTags.AFTER);
454                if (after != null)
455                        img.setSpacingAfter(Float.parseFloat(after));
456                img.setWidthPercentage(0);
457                return img;
458        }
459
460        /**
461         * @param tag
462         * @param chain
463         * @return the List
464         */
465        public List createList(final String tag, final ChainedProperties chain) {
466                List list;
467                if (HtmlTags.UL.equalsIgnoreCase(tag)) {
468                        list = new List(List.UNORDERED);
469                        list.setListSymbol("\u2022 ");
470                }
471                else {
472                        list = new List(List.ORDERED);
473                }
474                try{
475                        list.setIndentationLeft(new Float(chain.getProperty(HtmlTags.INDENT)).floatValue());
476                }catch (Exception e) {
477                        list.setAutoindent(true);
478                }
479                return list;
480        }
481}