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}