1 31 package org.pdfbox.pdmodel.interactive.form; 32 33 import java.io.ByteArrayInputStream ; 34 import java.io.ByteArrayOutputStream ; 35 import java.io.IOException ; 36 import java.io.OutputStream ; 37 import java.io.PrintWriter ; 38 39 import java.util.ArrayList ; 40 import java.util.Iterator ; 41 import java.util.List ; 42 import java.util.Map ; 43 44 import org.pdfbox.cos.COSArray; 45 import org.pdfbox.cos.COSDictionary; 46 import org.pdfbox.cos.COSFloat; 47 import org.pdfbox.cos.COSName; 48 import org.pdfbox.cos.COSNumber; 49 import org.pdfbox.cos.COSStream; 50 import org.pdfbox.cos.COSString; 51 52 import org.pdfbox.pdfparser.PDFStreamParser; 53 import org.pdfbox.pdfwriter.ContentStreamWriter; 54 55 import org.pdfbox.pdmodel.PDResources; 56 57 import org.pdfbox.pdmodel.common.PDRectangle; 58 59 import org.pdfbox.pdmodel.font.PDFont; 60 import org.pdfbox.pdmodel.font.PDFontDescriptor; 61 import org.pdfbox.pdmodel.font.PDSimpleFont; 62 63 import org.pdfbox.pdmodel.interactive.action.PDAdditionalActions; 64 import org.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary; 65 import org.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; 66 import org.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; 67 68 import org.pdfbox.util.PDFOperator; 69 70 83 public class PDAppearance 84 { 85 private PDVariableText parent; 86 87 private String value; 88 private COSString defaultAppearance; 89 90 private PDAcroForm acroForm; 91 private List widgets = new ArrayList (); 92 93 94 101 public PDAppearance( PDAcroForm theAcroForm, PDVariableText field ) throws IOException 102 { 103 acroForm = theAcroForm; 104 parent = field; 105 106 widgets = field.getKids(); 107 if( widgets == null ) 108 { 109 widgets = new ArrayList (); 110 widgets.add( field.getWidget() ); 111 } 112 113 defaultAppearance = getDefaultAppearance(); 114 115 116 } 117 118 123 private COSString getDefaultAppearance() 124 { 125 126 COSString dap = parent.getDefaultAppearance(); 127 if (dap == null) 128 { 129 COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( "Kids" ); 130 if( kids != null && kids.size() > 0 ) 131 { 132 COSDictionary firstKid = (COSDictionary)kids.getObject( 0 ); 133 dap = (COSString)firstKid.getDictionaryObject( "DA" ); 134 } 135 if( dap == null ) 136 { 137 dap = (COSString) acroForm.getDictionary().getDictionaryObject(COSName.getPDFName("DA")); 138 } 139 } 140 return dap; 141 } 142 143 private int getQ() 144 { 145 int q = parent.getQ(); 146 if( parent.getDictionary().getDictionaryObject( "Q" ) == null ) 147 { 148 COSArray kids = (COSArray)parent.getDictionary().getDictionaryObject( "Kids" ); 149 if( kids != null && kids.size() > 0 ) 150 { 151 COSDictionary firstKid = (COSDictionary)kids.getObject( 0 ); 152 COSNumber qNum = (COSNumber)firstKid.getDictionaryObject( "Q" ); 153 if( qNum != null ) 154 { 155 q = qNum.intValue(); 156 } 157 } 158 } 159 return q; 160 } 161 162 167 private List getStreamTokens( PDAppearanceStream appearanceStream ) throws IOException 168 { 169 List tokens = null; 170 if( appearanceStream != null ) 171 { 172 tokens = getStreamTokens( appearanceStream.getStream() ); 173 } 174 return tokens; 175 } 176 177 private List getStreamTokens( COSString string ) throws IOException 178 { 179 PDFStreamParser parser; 180 181 List tokens = null; 182 if( string != null ) 183 { 184 ByteArrayInputStream stream = new ByteArrayInputStream ( string.getBytes() ); 185 parser = new PDFStreamParser( stream, acroForm.getDocument().getDocument().getScratchFile() ); 186 parser.parse(); 187 tokens = parser.getTokens(); 188 } 189 return tokens; 190 } 191 192 private List getStreamTokens( COSStream stream ) throws IOException 193 { 194 PDFStreamParser parser; 195 196 List tokens = null; 197 if( stream != null ) 198 { 199 parser = new PDFStreamParser( stream ); 200 parser.parse(); 201 tokens = parser.getTokens(); 202 } 203 return tokens; 204 } 205 206 211 private boolean containsMarkedContent( List stream ) 212 { 213 return stream.contains( PDFOperator.getOperator( "BMC" ) ); 214 } 215 216 223 public void setAppearanceValue(String apValue) throws IOException 224 { 225 if ( parent.isMultiline() && apValue.indexOf('\n') != -1 ) 227 { 228 apValue = convertToMultiLine( apValue ); 229 } 230 231 value = apValue; 232 Iterator widgetIter = widgets.iterator(); 233 while( widgetIter.hasNext() ) 234 { 235 Object next = widgetIter.next(); 236 PDAnnotationWidget widget = null; 237 if( next instanceof PDField ) 238 { 239 widget = ((PDField)next).getWidget(); 240 } 241 else 242 { 243 widget = (PDAnnotationWidget)next; 244 } 245 PDAdditionalActions actions = widget.getActions(); 246 if( actions != null && 247 actions.getF() != null && 248 widget.getDictionary().getDictionaryObject( "AP" ) ==null) 249 { 250 } 253 else 254 { 255 256 PDAppearanceDictionary appearance = widget.getAppearance(); 257 if( appearance == null ) 258 { 259 appearance = new PDAppearanceDictionary(); 260 widget.setAppearance( appearance ); 261 } 262 263 Map normalAppearance = appearance.getNormalAppearance(); 264 PDAppearanceStream appearanceStream = (PDAppearanceStream)normalAppearance.get( "default" ); 265 if( appearanceStream == null ) 266 { 267 COSStream cosStream = new COSStream( acroForm.getDocument().getDocument().getScratchFile() ); 268 appearanceStream = new PDAppearanceStream( cosStream ); 269 appearanceStream.setBoundingBox( widget.getRectangle().createRetranslatedRectangle() ); 270 appearance.setNormalAppearance( appearanceStream ); 271 } 272 273 List tokens = getStreamTokens( appearanceStream ); 274 List daTokens = getStreamTokens( getDefaultAppearance() ); 275 PDFont pdFont = getFontAndUpdateResources( tokens, appearanceStream ); 276 277 if (!containsMarkedContent( tokens )) 278 { 279 ByteArrayOutputStream output = new ByteArrayOutputStream (); 280 281 ContentStreamWriter writer = new ContentStreamWriter( output ); 285 writer.writeTokens( tokens ); 286 287 output.write( " /Tx BMC\n".getBytes() ); 288 insertGeneratedAppearance( widget, output, pdFont, tokens, appearanceStream ); 289 output.write( " EMC".getBytes() ); 290 writeToStream( output.toByteArray(), appearanceStream ); 291 } 292 else 293 { 294 if( tokens != null ) 295 { 296 if( daTokens != null ) 297 { 298 int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" )); 299 int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" )); 300 if( bmcIndex != -1 && emcIndex != -1 && 301 emcIndex == bmcIndex+1 ) 302 { 303 tokens.addAll( emcIndex, daTokens ); 306 } 307 } 308 ByteArrayOutputStream output = new ByteArrayOutputStream (); 309 ContentStreamWriter writer = new ContentStreamWriter( output ); 310 float fontSize = calculateFontSize( pdFont, appearanceStream.getBoundingBox(), tokens, null ); 311 boolean foundString = false; 312 for( int i=0; i<tokens.size(); i++ ) 313 { 314 if( tokens.get( i ) instanceof COSString ) 315 { 316 foundString = true; 317 COSString drawnString =((COSString)tokens.get(i)); 318 drawnString.reset(); 319 drawnString.append( apValue.getBytes() ); 320 } 321 } 322 int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" )); 323 tokens.set( setFontIndex-1, new COSFloat( fontSize ) ); 324 if( foundString ) 325 { 326 writer.writeTokens( tokens ); 327 } 328 else 329 { 330 int bmcIndex = tokens.indexOf( PDFOperator.getOperator( "BMC" ) ); 331 int emcIndex = tokens.indexOf( PDFOperator.getOperator( "EMC" ) ); 332 333 if( bmcIndex != -1 ) 334 { 335 writer.writeTokens( tokens, 0, bmcIndex+1 ); 336 } 337 else 338 { 339 writer.writeTokens( tokens ); 340 } 341 output.write( "\n".getBytes() ); 342 insertGeneratedAppearance( widget, output, 343 pdFont, tokens, appearanceStream ); 344 if( emcIndex != -1 ) 345 { 346 writer.writeTokens( tokens, emcIndex, tokens.size() ); 347 } 348 } 349 writeToStream( output.toByteArray(), appearanceStream ); 350 } 351 else 352 { 353 } 355 } 356 } 357 } 358 } 359 360 private void insertGeneratedAppearance( PDAnnotationWidget fieldWidget, OutputStream output, 361 PDFont pdFont, List tokens, PDAppearanceStream appearanceStream ) throws IOException 362 { 363 PrintWriter printWriter = new PrintWriter ( output, true ); 364 float fontSize = 0.0f; 365 PDRectangle boundingBox = null; 366 boundingBox = appearanceStream.getBoundingBox(); 367 if( boundingBox == null ) 368 { 369 boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle(); 370 } 371 printWriter.println( "BT" ); 372 if( defaultAppearance != null ) 373 { 374 String daString = defaultAppearance.getString(); 375 PDFStreamParser daParser = new PDFStreamParser(new ByteArrayInputStream ( daString.getBytes() ), null ); 376 daParser.parse(); 377 List daTokens = daParser.getTokens(); 378 fontSize = calculateFontSize( pdFont, boundingBox, tokens, daTokens ); 379 int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) ); 380 if(fontIndex != -1 ) 381 { 382 daTokens.set( fontIndex-1, new COSFloat( fontSize ) ); 383 } 384 ContentStreamWriter daWriter = new ContentStreamWriter(output); 385 daWriter.writeTokens( daTokens ); 386 } 387 printWriter.println( getTextPosition( boundingBox, pdFont, fontSize, tokens ) ); 388 int q = getQ(); 389 if( q == PDTextbox.QUADDING_LEFT ) 390 { 391 } 393 else if( q == PDTextbox.QUADDING_CENTERED || 394 q == PDTextbox.QUADDING_RIGHT ) 395 { 396 float fieldWidth = boundingBox.getWidth(); 397 float stringWidth = (pdFont.getStringWidth( value )/1000)*fontSize; 398 float adjustAmount = fieldWidth - stringWidth - 4; 399 400 if( q == PDTextbox.QUADDING_CENTERED ) 401 { 402 adjustAmount = adjustAmount/2.0f; 403 } 404 405 printWriter.println( adjustAmount + " 0 Td" ); 406 } 407 else 408 { 409 throw new IOException ( "Error: Unknown justification value:" + q ); 410 } 411 printWriter.println("(" + value + ") Tj"); 412 printWriter.println("ET" ); 413 printWriter.flush(); 414 } 415 416 private PDFont getFontAndUpdateResources( List tokens, PDAppearanceStream appearanceStream ) throws IOException 417 { 418 419 PDFont retval = null; 420 PDResources streamResources = appearanceStream.getResources(); 421 PDResources formResources = acroForm.getDefaultResources(); 422 if( formResources != null ) 423 { 424 if( streamResources == null ) 425 { 426 streamResources = new PDResources(); 427 appearanceStream.setResources( streamResources ); 428 } 429 430 COSString da = getDefaultAppearance(); 431 if( da != null ) 432 { 433 String data = da.getString(); 434 PDFStreamParser streamParser = new PDFStreamParser( 435 new ByteArrayInputStream ( data.getBytes() ), null ); 436 streamParser.parse(); 437 tokens = streamParser.getTokens(); 438 } 439 440 int setFontIndex = tokens.indexOf( PDFOperator.getOperator( "Tf" )); 441 COSName cosFontName = (COSName)tokens.get( setFontIndex-2 ); 442 String fontName = cosFontName.getName(); 443 retval = (PDFont)streamResources.getFonts().get( fontName ); 444 if( retval == null ) 445 { 446 retval = (PDFont)formResources.getFonts().get( fontName ); 447 streamResources.getFonts().put( fontName, retval ); 448 } 449 } 450 return retval; 451 } 452 453 private String convertToMultiLine( String line ) 454 { 455 int currIdx = 0; 456 int lastIdx = 0; 457 StringBuffer result = new StringBuffer (line.length() + 64); 458 while( (currIdx = line.indexOf('\n',lastIdx )) > -1 ) 459 { 460 result.append(line.substring(lastIdx,currIdx)); 461 result.append(" ) Tj\n0 -13 Td\n("); 462 lastIdx = currIdx + 1; 463 } 464 result.append(line.substring(lastIdx)); 465 return result.toString(); 466 } 467 468 473 private void writeToStream( byte[] data, PDAppearanceStream appearanceStream ) throws IOException 474 { 475 OutputStream out = appearanceStream.getStream().createUnfilteredStream(); 476 out.write( data ); 477 out.flush(); 478 } 479 480 481 485 private float getLineWidth( List tokens ) 486 { 487 488 float retval = 1; 489 if( tokens != null ) 490 { 491 int btIndex = tokens.indexOf(PDFOperator.getOperator( "BT" )); 492 int wIndex = tokens.indexOf(PDFOperator.getOperator( "w" )); 493 if( (wIndex > 0) && (wIndex < btIndex) ) 495 { 496 retval = ((COSNumber)tokens.get(wIndex-1)).floatValue(); 497 } 498 } 499 return retval; 500 } 501 502 private PDRectangle getSmallestDrawnRectangle( PDRectangle boundingBox, List tokens ) 503 { 504 PDRectangle smallest = boundingBox; 505 for( int i=0; i<tokens.size(); i++ ) 506 { 507 Object next = tokens.get( i ); 508 if( next == PDFOperator.getOperator( "re" ) ) 509 { 510 COSNumber x = (COSNumber)tokens.get( i-4 ); 511 COSNumber y = (COSNumber)tokens.get( i-3 ); 512 COSNumber width = (COSNumber)tokens.get( i-2 ); 513 COSNumber height = (COSNumber)tokens.get( i-1 ); 514 PDRectangle potentialSmallest = new PDRectangle(); 515 potentialSmallest.setLowerLeftX( x.floatValue() ); 516 potentialSmallest.setLowerLeftY( y.floatValue() ); 517 potentialSmallest.setUpperRightX( x.floatValue() + width.floatValue() ); 518 potentialSmallest.setUpperRightY( y.floatValue() + height.floatValue() ); 519 if( smallest == null || 520 smallest.getLowerLeftX() < potentialSmallest.getLowerLeftX() || 521 smallest.getUpperRightY() > potentialSmallest.getUpperRightY() ) 522 { 523 smallest = potentialSmallest; 524 } 525 526 } 527 } 528 return smallest; 529 } 530 531 538 private float calculateFontSize( PDFont pdFont, PDRectangle boundingBox, List tokens, List daTokens ) 539 throws IOException 540 { 541 float fontSize = 0; 542 if( daTokens != null ) 543 { 544 546 int fontIndex = daTokens.indexOf( PDFOperator.getOperator( "Tf" ) ); 547 if(fontIndex != -1 ) 548 { 549 fontSize = ((COSNumber)daTokens.get(fontIndex-1)).floatValue(); 550 } 551 } 552 if( parent.doNotScroll() ) 553 { 554 float widthAtFontSize1 = pdFont.getStringWidth( value ); 556 float availableWidth = boundingBox.getWidth(); 557 float perfectFitFontSize = availableWidth / widthAtFontSize1; 558 } 559 else if( fontSize == 0 ) 560 { 561 float lineWidth = getLineWidth( tokens ); 562 float stringWidth = pdFont.getStringWidth( value ); 563 float height = 0; 564 if( pdFont instanceof PDSimpleFont ) 565 { 566 height = ((PDSimpleFont)pdFont).getFontDescriptor().getFontBoundingBox().getHeight(); 567 } 568 else 569 { 570 height = pdFont.getAverageFontWidth(); 573 } 574 height = height/1000f; 575 576 float availHeight = getAvailableHeight( boundingBox, lineWidth ); 577 fontSize =(availHeight/height); 578 } 579 return fontSize; 580 } 581 582 591 private String getTextPosition( PDRectangle boundingBox, PDFont pdFont, float fontSize, List tokens ) 592 throws IOException 593 { 594 float lineWidth = getLineWidth( tokens ); 595 float pos = 0.0f; 596 if(parent.isMultiline()) 597 { 598 int rows = (int) (getAvailableHeight( boundingBox, lineWidth ) / ((int) fontSize)); 599 pos = ((rows)*fontSize)-fontSize; 600 } 601 else 602 { 603 if( pdFont instanceof PDSimpleFont ) 604 { 605 PDFontDescriptor fd = ((PDSimpleFont)pdFont).getFontDescriptor(); 613 float bBoxHeight = boundingBox.getHeight(); 614 float fontHeight = fd.getFontBoundingBox().getHeight() + 2 * fd.getDescent(); 615 fontHeight = (fontHeight/1000) * fontSize; 616 pos = (bBoxHeight - fontHeight)/2; 617 } 618 else 619 { 620 throw new IOException ( "Error: Don't know how to calculate the position for non-simple fonts" ); 621 } 622 } 623 PDRectangle innerBox = getSmallestDrawnRectangle( boundingBox, tokens ); 624 float xInset = 2+ 2*(boundingBox.getWidth() - innerBox.getWidth()); 625 return Math.round(xInset) + " "+ pos + " Td"; 626 } 627 628 632 private float getAvailableWidth( PDRectangle boundingBox, float lineWidth ) 633 { 634 return boundingBox.getWidth() - 2 * lineWidth; 635 } 636 637 641 private float getAvailableHeight( PDRectangle boundingBox, float lineWidth ) 642 { 643 return boundingBox.getHeight() - 2 * lineWidth; 644 } 645 } | Popular Tags |