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}