001/* 002 * $Id: PdfContentStreamProcessor.java 4923 2011-07-05 15:13:19Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Kevin Day, 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.pdf.parser; 045 046import java.io.IOException; 047import java.util.ArrayList; 048import java.util.HashMap; 049import java.util.Iterator; 050import java.util.List; 051import java.util.Map; 052import java.util.Stack; 053 054import com.itextpdf.text.ExceptionConverter; 055import com.itextpdf.text.error_messages.MessageLocalization; 056import com.itextpdf.text.pdf.CMapAwareDocumentFont; 057import com.itextpdf.text.pdf.PRIndirectReference; 058import com.itextpdf.text.pdf.PRTokeniser; 059import com.itextpdf.text.pdf.PdfArray; 060import com.itextpdf.text.pdf.PdfContentParser; 061import com.itextpdf.text.pdf.PdfDictionary; 062import com.itextpdf.text.pdf.PdfIndirectReference; 063import com.itextpdf.text.pdf.PdfLiteral; 064import com.itextpdf.text.pdf.PdfName; 065import com.itextpdf.text.pdf.PdfNumber; 066import com.itextpdf.text.pdf.PdfObject; 067import com.itextpdf.text.pdf.PdfStream; 068import com.itextpdf.text.pdf.PdfString; 069 070/** 071 * Processor for a PDF content Stream. 072 * @since 2.1.4 073 */ 074public class PdfContentStreamProcessor { 075 /** 076 * Default operator 077 * @since 5.0.1 078 */ 079 public static final String DEFAULTOPERATOR = "DefaultOperator"; 080 081 /** A map with all supported operators operators (PDF syntax). */ 082 final private Map<String, ContentOperator> operators; 083 /** Resources for the content stream. */ 084 private ResourceDictionary resources; 085 /** Stack keeping track of the graphics state. */ 086 private final Stack<GraphicsState> gsStack = new Stack<GraphicsState>(); 087 /** Text matrix. */ 088 private Matrix textMatrix; 089 /** Text line matrix. */ 090 private Matrix textLineMatrix; 091 /** Listener that will be notified of render events */ 092 final private RenderListener renderListener; 093 /** A map with all supported XObject handlers */ 094 final private Map<PdfName, XObjectDoHandler> xobjectDoHandlers; 095 /** 096 * The font cache. 097 * @since 5.0.6 098 */ 099 /** */ 100 final private Map<Integer,CMapAwareDocumentFont> cachedFonts = new HashMap<Integer, CMapAwareDocumentFont>(); 101 /** 102 * A stack containing marked content info. 103 * @since 5.0.2 104 */ 105 private final Stack<MarkedContentInfo> markedContentStack = new Stack<MarkedContentInfo>(); 106 107 /** 108 * Creates a new PDF Content Stream Processor that will send it's output to the 109 * designated render listener. 110 * 111 * @param renderListener the {@link RenderListener} that will receive rendering notifications 112 */ 113 public PdfContentStreamProcessor(RenderListener renderListener) { 114 this.renderListener = renderListener; 115 operators = new HashMap<String, ContentOperator>(); 116 populateOperators(); 117 xobjectDoHandlers = new HashMap<PdfName, XObjectDoHandler>(); 118 populateXObjectDoHandlers(); 119 reset(); 120 } 121 122 private void populateXObjectDoHandlers(){ 123 registerXObjectDoHandler(PdfName.DEFAULT, new IgnoreXObjectDoHandler()); 124 registerXObjectDoHandler(PdfName.FORM, new FormXObjectDoHandler()); 125 registerXObjectDoHandler(PdfName.IMAGE, new ImageXObjectDoHandler()); 126 } 127 128 /** 129 * Registers a Do handler that will be called when Do for the provided XObject subtype is encountered during content processing. 130 * <br> 131 * If you register a handler, it is a very good idea to pass the call on to the existing registered handler (returned by this call), otherwise you 132 * may inadvertently change the internal behavior of the processor. 133 * @param xobjectSubType the XObject subtype this handler will process, or PdfName.DEFAULT for a catch-all handler 134 * @param handler the handler that will receive notification when the Do operator for the specified subtype is encountered 135 * @return the existing registered handler, if any 136 * @since 5.0.1 137 */ 138 public XObjectDoHandler registerXObjectDoHandler(PdfName xobjectSubType, XObjectDoHandler handler){ 139 return xobjectDoHandlers.put(xobjectSubType, handler); 140 } 141 142 /** 143 * Gets the font pointed to by the indirect reference. The font may have been cached. 144 * @param ind the indirect reference ponting to the font 145 * @return the font 146 * @since 5.0.6 147 */ 148 public CMapAwareDocumentFont getFont(PRIndirectReference ind) { 149 Integer n = Integer.valueOf(ind.getNumber()); 150 CMapAwareDocumentFont font = cachedFonts.get(n); 151 if (font == null) { 152 font = new CMapAwareDocumentFont(ind); 153 cachedFonts.put(n, font); 154 } 155 return font; 156 } 157 158 /** 159 * Loads all the supported graphics and text state operators in a map. 160 */ 161 private void populateOperators(){ 162 163 registerContentOperator(DEFAULTOPERATOR, new IgnoreOperatorContentOperator()); 164 165 registerContentOperator("q", new PushGraphicsState()); 166 registerContentOperator("Q", new PopGraphicsState()); 167 registerContentOperator("cm", new ModifyCurrentTransformationMatrix()); 168 registerContentOperator("gs", new ProcessGraphicsStateResource()); 169 170 SetTextCharacterSpacing tcOperator = new SetTextCharacterSpacing(); 171 registerContentOperator("Tc", tcOperator); 172 SetTextWordSpacing twOperator = new SetTextWordSpacing(); 173 registerContentOperator("Tw", twOperator); 174 registerContentOperator("Tz", new SetTextHorizontalScaling()); 175 SetTextLeading tlOperator = new SetTextLeading(); 176 registerContentOperator("TL", tlOperator); 177 registerContentOperator("Tf", new SetTextFont()); 178 registerContentOperator("Tr", new SetTextRenderMode()); 179 registerContentOperator("Ts", new SetTextRise()); 180 181 registerContentOperator("BT", new BeginText()); 182 registerContentOperator("ET", new EndText()); 183 registerContentOperator("BMC", new BeginMarkedContent()); 184 registerContentOperator("BDC", new BeginMarkedContentDictionary()); 185 registerContentOperator("EMC", new EndMarkedContent()); 186 187 TextMoveStartNextLine tdOperator = new TextMoveStartNextLine(); 188 registerContentOperator("Td", tdOperator); 189 registerContentOperator("TD", new TextMoveStartNextLineWithLeading(tdOperator, tlOperator)); 190 registerContentOperator("Tm", new TextSetTextMatrix()); 191 TextMoveNextLine tstarOperator = new TextMoveNextLine(tdOperator); 192 registerContentOperator("T*", tstarOperator); 193 194 ShowText tjOperator = new ShowText(); 195 registerContentOperator("Tj", new ShowText()); 196 MoveNextLineAndShowText tickOperator = new MoveNextLineAndShowText(tstarOperator, tjOperator); 197 registerContentOperator("'", tickOperator); 198 registerContentOperator("\"", new MoveNextLineAndShowTextWithSpacing(twOperator, tcOperator, tickOperator)); 199 registerContentOperator("TJ", new ShowTextArray()); 200 201 registerContentOperator("Do", new Do()); 202 } 203 204 /** 205 * Registers a content operator that will be called when the specified operator string is encountered during content processing. 206 * <br> 207 * If you register an operator, it is a very good idea to pass the call on to the existing registered operator (returned by this call), otherwise you 208 * may inadvertently change the internal behavior of the processor. 209 * @param operatorString the operator id, or DEFAULTOPERATOR for a catch-all operator 210 * @param operator the operator that will receive notification when the operator is encountered 211 * @return the existing registered operator, if any 212 * @since 2.1.7 213 */ 214 public ContentOperator registerContentOperator(String operatorString, ContentOperator operator){ 215 return operators.put(operatorString, operator); 216 } 217 218 /** 219 * Resets the graphics state stack, matrices and resources. 220 */ 221 public void reset(){ 222 gsStack.removeAllElements(); 223 gsStack.add(new GraphicsState()); 224 textMatrix = null; 225 textLineMatrix = null; 226 resources = new ResourceDictionary(); 227 } 228 229 /** 230 * Returns the current graphics state. 231 * @return the graphics state 232 */ 233 private GraphicsState gs(){ 234 return gsStack.peek(); 235 } 236 237 /** 238 * Invokes an operator. 239 * @param operator the PDF Syntax of the operator 240 * @param operands a list with operands 241 */ 242 private void invokeOperator(PdfLiteral operator, ArrayList<PdfObject> operands) throws Exception{ 243 ContentOperator op = operators.get(operator.toString()); 244 if (op == null) 245 op = operators.get(DEFAULTOPERATOR); 246 op.invoke(this, operator, operands); 247 } 248 249 /** 250 * Add to the marked content stack 251 * @param tag the tag of the marked content 252 * @param dict the PdfDictionary associated with the marked content 253 * @since 5.0.2 254 */ 255 private void beginMarkedContent(PdfName tag, PdfDictionary dict) { 256 markedContentStack.push(new MarkedContentInfo(tag, dict)); 257 } 258 259 /** 260 * Remove the latest marked content from the stack. Keeps track of the BMC, BDC and EMC operators. 261 * @since 5.0.2 262 */ 263 private void endMarkedContent() { 264 markedContentStack.pop(); 265 } 266 267 /** 268 * Decodes a PdfString (which will contain glyph ids encoded in the font's encoding) 269 * based on the active font, and determine the unicode equivalent 270 * @param in the String that needs to be encoded 271 * @return the encoded String 272 * @since 2.1.7 273 */ 274 private String decode(PdfString in){ 275 byte[] bytes = in.getBytes(); 276 return gs().font.decode(bytes, 0, bytes.length); 277 } 278 279 /** 280 * Used to trigger beginTextBlock on the renderListener 281 */ 282 private void beginText(){ 283 renderListener.beginTextBlock(); 284 } 285 286 /** 287 * Used to trigger endTextBlock on the renderListener 288 */ 289 private void endText(){ 290 renderListener.endTextBlock(); 291 } 292 293 /** 294 * Displays text. 295 * @param string the text to display 296 */ 297 private void displayPdfString(PdfString string){ 298 299 String unicode = decode(string); 300 301 TextRenderInfo renderInfo = new TextRenderInfo(unicode, gs(), textMatrix, markedContentStack); 302 303 renderListener.renderText(renderInfo); 304 305 textMatrix = new Matrix(renderInfo.getUnscaledWidth(), 0).multiply(textMatrix); 306 } 307 308 309 310 311 312 /** 313 * Displays an XObject using the registered handler for this XObject's subtype 314 * @param xobjectName the name of the XObject to retrieve from the resource dictionary 315 */ 316 private void displayXObject(PdfName xobjectName) throws IOException { 317 PdfDictionary xobjects = resources.getAsDict(PdfName.XOBJECT); 318 PdfObject xobject = xobjects.getDirectObject(xobjectName); 319 PdfStream xobjectStream = (PdfStream)xobject; 320 321 PdfName subType = xobjectStream.getAsName(PdfName.SUBTYPE); 322 if (xobject.isStream()){ 323 XObjectDoHandler handler = xobjectDoHandlers.get(subType); 324 if (handler == null) 325 handler = xobjectDoHandlers.get(PdfName.DEFAULT); 326 handler.handleXObject(this, xobjectStream, xobjects.getAsIndirectObject(xobjectName)); 327 } else { 328 throw new IllegalStateException(MessageLocalization.getComposedMessage("XObject.1.is.not.a.stream", xobjectName)); 329 } 330 331 } 332 333 /** 334 * Adjusts the text matrix for the specified adjustment value (see TJ operator in the PDF spec for information) 335 * @param tj the text adjustment 336 */ 337 private void applyTextAdjust(float tj){ 338 float adjustBy = -tj/1000f * gs().fontSize * gs().horizontalScaling; 339 340 textMatrix = new Matrix(adjustBy, 0).multiply(textMatrix); 341 } 342 343 344 345 346 347 /** 348 * Processes PDF syntax 349 * @param contentBytes the bytes of a content stream 350 * @param resources the resources that come with the content stream 351 */ 352 public void processContent(byte[] contentBytes, PdfDictionary resources){ 353 this.resources.push(resources); 354 try { 355 PRTokeniser tokeniser = new PRTokeniser(contentBytes); 356 PdfContentParser ps = new PdfContentParser(tokeniser); 357 ArrayList<PdfObject> operands = new ArrayList<PdfObject>(); 358 while (ps.parse(operands).size() > 0){ 359 PdfLiteral operator = (PdfLiteral)operands.get(operands.size()-1); 360 if ("BI".equals(operator.toString())){ 361 // we don't call invokeOperator for embedded images - this is one area of the PDF spec that is particularly nasty and inconsistent 362 PdfDictionary colorSpaceDic = resources.getAsDict(PdfName.COLORSPACE); 363 ImageRenderInfo renderInfo = ImageRenderInfo.createdForEmbeddedImage(gs().ctm, InlineImageUtils.parseInlineImage(ps, colorSpaceDic)); 364 renderListener.renderImage(renderInfo); 365 } else { 366 invokeOperator(operator, operands); 367 } 368 } 369 370 } 371 catch (Exception e) { 372 throw new ExceptionConverter(e); 373 } 374 this.resources.pop(); 375 376 } 377 378 379 380 /** 381 * A resource dictionary that allows stack-like behavior to support resource dictionary inheritance 382 */ 383 private static class ResourceDictionary extends PdfDictionary{ 384 private final List<PdfDictionary> resourcesStack = new ArrayList<PdfDictionary>(); 385 public ResourceDictionary() { 386 } 387 388 public void push(PdfDictionary resources){ 389 resourcesStack.add(resources); 390 } 391 392 public void pop(){ 393 resourcesStack.remove(resourcesStack.size()-1); 394 } 395 396 @Override 397 public PdfObject getDirectObject(PdfName key) { 398 for (int i = resourcesStack.size() - 1; i >= 0; i--){ 399 PdfDictionary subResource = resourcesStack.get(i); 400 if (subResource != null){ 401 PdfObject obj = subResource.getDirectObject(key); 402 if (obj != null) return obj; 403 } 404 } 405 return super.getDirectObject(key); // shouldn't be necessary, but just in case we've done something crazy 406 } 407 } 408 409 /** 410 * A content operator implementation (unregistered). 411 */ 412 private static class IgnoreOperatorContentOperator implements ContentOperator{ 413 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands){ 414 // ignore the operator 415 } 416 } 417 418 /** 419 * A content operator implementation (TJ). 420 */ 421 private static class ShowTextArray implements ContentOperator{ 422 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 423 PdfArray array = (PdfArray)operands.get(0); 424 float tj = 0; 425 for (Iterator<PdfObject> i = array.listIterator(); i.hasNext(); ) { 426 PdfObject entryObj = i.next(); 427 if (entryObj instanceof PdfString){ 428 processor.displayPdfString((PdfString)entryObj); 429 tj = 0; 430 } else { 431 tj = ((PdfNumber)entryObj).floatValue(); 432 processor.applyTextAdjust(tj); 433 } 434 } 435 436 } 437 } 438 439 /** 440 * A content operator implementation ("). 441 */ 442 private static class MoveNextLineAndShowTextWithSpacing implements ContentOperator{ 443 private final SetTextWordSpacing setTextWordSpacing; 444 private final SetTextCharacterSpacing setTextCharacterSpacing; 445 private final MoveNextLineAndShowText moveNextLineAndShowText; 446 447 public MoveNextLineAndShowTextWithSpacing(SetTextWordSpacing setTextWordSpacing, SetTextCharacterSpacing setTextCharacterSpacing, MoveNextLineAndShowText moveNextLineAndShowText) { 448 this.setTextWordSpacing = setTextWordSpacing; 449 this.setTextCharacterSpacing = setTextCharacterSpacing; 450 this.moveNextLineAndShowText = moveNextLineAndShowText; 451 } 452 453 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 454 PdfNumber aw = (PdfNumber)operands.get(0); 455 PdfNumber ac = (PdfNumber)operands.get(1); 456 PdfString string = (PdfString)operands.get(2); 457 458 ArrayList<PdfObject> twOperands = new ArrayList<PdfObject>(1); 459 twOperands.add(0, aw); 460 setTextWordSpacing.invoke(processor, null, twOperands); 461 462 ArrayList<PdfObject> tcOperands = new ArrayList<PdfObject>(1); 463 tcOperands.add(0, ac); 464 setTextCharacterSpacing.invoke(processor, null, tcOperands); 465 466 ArrayList<PdfObject> tickOperands = new ArrayList<PdfObject>(1); 467 tickOperands.add(0, string); 468 moveNextLineAndShowText.invoke(processor, null, tickOperands); 469 } 470 } 471 472 /** 473 * A content operator implementation ('). 474 */ 475 private static class MoveNextLineAndShowText implements ContentOperator{ 476 private final TextMoveNextLine textMoveNextLine; 477 private final ShowText showText; 478 public MoveNextLineAndShowText(TextMoveNextLine textMoveNextLine, ShowText showText) { 479 this.textMoveNextLine = textMoveNextLine; 480 this.showText = showText; 481 } 482 483 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 484 textMoveNextLine.invoke(processor, null, new ArrayList<PdfObject>(0)); 485 showText.invoke(processor, null, operands); 486 } 487 } 488 489 /** 490 * A content operator implementation (Tj). 491 */ 492 private static class ShowText implements ContentOperator{ 493 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 494 PdfString string = (PdfString)operands.get(0); 495 496 processor.displayPdfString(string); 497 } 498 } 499 500 501 /** 502 * A content operator implementation (T*). 503 */ 504 private static class TextMoveNextLine implements ContentOperator{ 505 private final TextMoveStartNextLine moveStartNextLine; 506 public TextMoveNextLine(TextMoveStartNextLine moveStartNextLine){ 507 this.moveStartNextLine = moveStartNextLine; 508 } 509 510 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 511 ArrayList<PdfObject> tdoperands = new ArrayList<PdfObject>(2); 512 tdoperands.add(0, new PdfNumber(0)); 513 tdoperands.add(1, new PdfNumber(-processor.gs().leading)); 514 moveStartNextLine.invoke(processor, null, tdoperands); 515 } 516 } 517 518 /** 519 * A content operator implementation (Tm). 520 */ 521 private static class TextSetTextMatrix implements ContentOperator{ 522 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 523 float a = ((PdfNumber)operands.get(0)).floatValue(); 524 float b = ((PdfNumber)operands.get(1)).floatValue(); 525 float c = ((PdfNumber)operands.get(2)).floatValue(); 526 float d = ((PdfNumber)operands.get(3)).floatValue(); 527 float e = ((PdfNumber)operands.get(4)).floatValue(); 528 float f = ((PdfNumber)operands.get(5)).floatValue(); 529 530 processor.textLineMatrix = new Matrix(a, b, c, d, e, f); 531 processor.textMatrix = processor.textLineMatrix; 532 } 533 } 534 535 /** 536 * A content operator implementation (TD). 537 */ 538 private static class TextMoveStartNextLineWithLeading implements ContentOperator{ 539 private final TextMoveStartNextLine moveStartNextLine; 540 private final SetTextLeading setTextLeading; 541 public TextMoveStartNextLineWithLeading(TextMoveStartNextLine moveStartNextLine, SetTextLeading setTextLeading){ 542 this.moveStartNextLine = moveStartNextLine; 543 this.setTextLeading = setTextLeading; 544 } 545 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 546 float ty = ((PdfNumber)operands.get(1)).floatValue(); 547 548 ArrayList<PdfObject> tlOperands = new ArrayList<PdfObject>(1); 549 tlOperands.add(0, new PdfNumber(-ty)); 550 setTextLeading.invoke(processor, null, tlOperands); 551 moveStartNextLine.invoke(processor, null, operands); 552 } 553 } 554 555 /** 556 * A content operator implementation (Td). 557 */ 558 private static class TextMoveStartNextLine implements ContentOperator{ 559 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 560 float tx = ((PdfNumber)operands.get(0)).floatValue(); 561 float ty = ((PdfNumber)operands.get(1)).floatValue(); 562 563 Matrix translationMatrix = new Matrix(tx, ty); 564 processor.textMatrix = translationMatrix.multiply(processor.textLineMatrix); 565 processor.textLineMatrix = processor.textMatrix; 566 } 567 } 568 569 /** 570 * A content operator implementation (Tf). 571 */ 572 private static class SetTextFont implements ContentOperator{ 573 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 574 PdfName fontResourceName = (PdfName)operands.get(0); 575 float size = ((PdfNumber)operands.get(1)).floatValue(); 576 577 PdfDictionary fontsDictionary = processor.resources.getAsDict(PdfName.FONT); 578 CMapAwareDocumentFont font = processor.getFont((PRIndirectReference)fontsDictionary.get(fontResourceName)); 579 580 processor.gs().font = font; 581 processor.gs().fontSize = size; 582 583 } 584 } 585 586 /** 587 * A content operator implementation (Tr). 588 */ 589 private static class SetTextRenderMode implements ContentOperator{ 590 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 591 PdfNumber render = (PdfNumber)operands.get(0); 592 processor.gs().renderMode = render.intValue(); 593 } 594 } 595 596 /** 597 * A content operator implementation (Ts). 598 */ 599 private static class SetTextRise implements ContentOperator{ 600 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 601 PdfNumber rise = (PdfNumber)operands.get(0); 602 processor.gs().rise = rise.floatValue(); 603 } 604 } 605 606 /** 607 * A content operator implementation (TL). 608 */ 609 private static class SetTextLeading implements ContentOperator{ 610 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 611 PdfNumber leading = (PdfNumber)operands.get(0); 612 processor.gs().leading = leading.floatValue(); 613 } 614 } 615 616 /** 617 * A content operator implementation (Tz). 618 */ 619 private static class SetTextHorizontalScaling implements ContentOperator{ 620 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 621 PdfNumber scale = (PdfNumber)operands.get(0); 622 processor.gs().horizontalScaling = scale.floatValue()/100f; 623 } 624 } 625 626 /** 627 * A content operator implementation (Tc). 628 */ 629 private static class SetTextCharacterSpacing implements ContentOperator{ 630 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 631 PdfNumber charSpace = (PdfNumber)operands.get(0); 632 processor.gs().characterSpacing = charSpace.floatValue(); 633 } 634 } 635 636 /** 637 * A content operator implementation (Tw). 638 */ 639 private static class SetTextWordSpacing implements ContentOperator{ 640 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 641 PdfNumber wordSpace = (PdfNumber)operands.get(0); 642 processor.gs().wordSpacing = wordSpace.floatValue(); 643 } 644 } 645 646 /** 647 * A content operator implementation (gs). 648 */ 649 private static class ProcessGraphicsStateResource implements ContentOperator{ 650 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 651 652 PdfName dictionaryName = (PdfName)operands.get(0); 653 PdfDictionary extGState = processor.resources.getAsDict(PdfName.EXTGSTATE); 654 if (extGState == null) 655 throw new IllegalArgumentException(MessageLocalization.getComposedMessage("resources.do.not.contain.extgstate.entry.unable.to.process.operator.1", operator)); 656 PdfDictionary gsDic = extGState.getAsDict(dictionaryName); 657 if (gsDic == null) 658 throw new IllegalArgumentException(MessageLocalization.getComposedMessage("1.is.an.unknown.graphics.state.dictionary", dictionaryName)); 659 660 // at this point, all we care about is the FONT entry in the GS dictionary 661 PdfArray fontParameter = gsDic.getAsArray(PdfName.FONT); 662 if (fontParameter != null){ 663 CMapAwareDocumentFont font = processor.getFont((PRIndirectReference)fontParameter.getPdfObject(0)); 664 float size = fontParameter.getAsNumber(1).floatValue(); 665 666 processor.gs().font = font; 667 processor.gs().fontSize = size; 668 } 669 } 670 } 671 672 /** 673 * A content operator implementation (q). 674 */ 675 private static class PushGraphicsState implements ContentOperator{ 676 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 677 GraphicsState gs = processor.gsStack.peek(); 678 GraphicsState copy = new GraphicsState(gs); 679 processor.gsStack.push(copy); 680 } 681 } 682 683 /** 684 * A content operator implementation (cm). 685 */ 686 private static class ModifyCurrentTransformationMatrix implements ContentOperator{ 687 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 688 float a = ((PdfNumber)operands.get(0)).floatValue(); 689 float b = ((PdfNumber)operands.get(1)).floatValue(); 690 float c = ((PdfNumber)operands.get(2)).floatValue(); 691 float d = ((PdfNumber)operands.get(3)).floatValue(); 692 float e = ((PdfNumber)operands.get(4)).floatValue(); 693 float f = ((PdfNumber)operands.get(5)).floatValue(); 694 Matrix matrix = new Matrix(a, b, c, d, e, f); 695 GraphicsState gs = processor.gsStack.peek(); 696 gs.ctm = matrix.multiply(gs.ctm); 697 } 698 } 699 700 /** 701 * A content operator implementation (Q). 702 */ 703 private static class PopGraphicsState implements ContentOperator{ 704 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 705 processor.gsStack.pop(); 706 } 707 } 708 709 /** 710 * A content operator implementation (BT). 711 */ 712 private static class BeginText implements ContentOperator{ 713 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 714 processor.textMatrix = new Matrix(); 715 processor.textLineMatrix = processor.textMatrix; 716 processor.beginText(); 717 } 718 } 719 720 /** 721 * A content operator implementation (ET). 722 */ 723 private static class EndText implements ContentOperator{ 724 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) { 725 processor.textMatrix = null; 726 processor.textLineMatrix = null; 727 processor.endText(); 728 } 729 } 730 731 /** 732 * A content operator implementation (BMC). 733 * @since 5.0.2 734 */ 735 private static class BeginMarkedContent implements ContentOperator{ 736 737 public void invoke(PdfContentStreamProcessor processor, 738 PdfLiteral operator, ArrayList<PdfObject> operands) 739 throws Exception { 740 processor.beginMarkedContent((PdfName)operands.get(0), new PdfDictionary()); 741 } 742 743 } 744 745 /** 746 * A content operator implementation (BDC). 747 * @since 5.0.2 748 */ 749 private static class BeginMarkedContentDictionary implements ContentOperator{ 750 751 public void invoke(PdfContentStreamProcessor processor, 752 PdfLiteral operator, ArrayList<PdfObject> operands) 753 throws Exception { 754 755 PdfObject properties = operands.get(1); 756 757 processor.beginMarkedContent((PdfName)operands.get(0), getPropertiesDictionary(properties, processor.resources)); 758 } 759 760 private PdfDictionary getPropertiesDictionary(PdfObject operand1, ResourceDictionary resources){ 761 if (operand1.isDictionary()) 762 return (PdfDictionary)operand1; 763 764 PdfName dictionaryName = ((PdfName)operand1); 765 return resources.getAsDict(dictionaryName); 766 } 767 } 768 769 /** 770 * A content operator implementation (BMC). 771 * @since 5.0.2 772 */ 773 private static class EndMarkedContent implements ContentOperator{ 774 public void invoke(PdfContentStreamProcessor processor, 775 PdfLiteral operator, ArrayList<PdfObject> operands) 776 throws Exception { 777 processor.endMarkedContent(); 778 } 779 } 780 781 /** 782 * A content operator implementation (Do). 783 */ 784 private static class Do implements ContentOperator{ 785 public void invoke(PdfContentStreamProcessor processor, PdfLiteral operator, ArrayList<PdfObject> operands) throws IOException { 786 PdfName xobjectName = (PdfName)operands.get(0); 787 processor.displayXObject(xobjectName); 788 } 789 } 790 791 /** 792 * An XObject subtype handler for FORM 793 */ 794 private static class FormXObjectDoHandler implements XObjectDoHandler{ 795 796 public void handleXObject(PdfContentStreamProcessor processor, PdfStream stream, PdfIndirectReference ref) { 797 798 final PdfDictionary resources = stream.getAsDict(PdfName.RESOURCES); 799 800 // we read the content bytes up here so if it fails we don't leave the graphics state stack corrupted 801 // this is probably not necessary (if we fail on this, probably the entire content stream processing 802 // operation should be rejected 803 byte[] contentBytes; 804 try { 805 contentBytes = ContentByteUtils.getContentBytesFromContentObject(stream); 806 } catch (IOException e1) { 807 throw new ExceptionConverter(e1); 808 } 809 final PdfArray matrix = stream.getAsArray(PdfName.MATRIX); 810 811 new PushGraphicsState().invoke(processor, null, null); 812 813 if (matrix != null){ 814 float a = matrix.getAsNumber(0).floatValue(); 815 float b = matrix.getAsNumber(1).floatValue(); 816 float c = matrix.getAsNumber(2).floatValue(); 817 float d = matrix.getAsNumber(3).floatValue(); 818 float e = matrix.getAsNumber(4).floatValue(); 819 float f = matrix.getAsNumber(5).floatValue(); 820 Matrix formMatrix = new Matrix(a, b, c, d, e, f); 821 822 processor.gs().ctm = formMatrix.multiply(processor.gs().ctm); 823 } 824 825 processor.processContent(contentBytes, resources); 826 827 new PopGraphicsState().invoke(processor, null, null); 828 829 } 830 831 } 832 833 /** 834 * An XObject subtype handler for IMAGE 835 */ 836 private static class ImageXObjectDoHandler implements XObjectDoHandler{ 837 838 public void handleXObject(PdfContentStreamProcessor processor, PdfStream xobjectStream, PdfIndirectReference ref) { 839 ImageRenderInfo renderInfo = ImageRenderInfo.createForXObject(processor.gs().ctm, ref); 840 processor.renderListener.renderImage(renderInfo); 841 } 842 } 843 844 /** 845 * An XObject subtype handler that does nothing 846 */ 847 private static class IgnoreXObjectDoHandler implements XObjectDoHandler{ 848 public void handleXObject(PdfContentStreamProcessor processor, PdfStream xobjectStream, PdfIndirectReference ref) { 849 // ignore XObject subtype 850 } 851 } 852}