001: /*
002: Copyright © 2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
003:
004: Contributors:
005: * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it):
006: contributed code is Copyright © 2007 by Stefano Chizzolini.
007:
008: This file should be part of the source code distribution of "PDF Clown library"
009: (the Program): see the accompanying README files for more info.
010:
011: This Program is free software; you can redistribute it and/or modify it under
012: the terms of the GNU General Public License as published by the Free Software
013: Foundation; either version 2 of the License, or (at your option) any later version.
014:
015: This Program is distributed in the hope that it will be useful, but WITHOUT ANY
016: WARRANTY, either expressed or implied; without even the implied warranty of
017: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
018:
019: You should have received a copy of the GNU General Public License along with this
020: Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
021:
022: Redistribution and use, with or without modification, are permitted provided that such
023: redistributions retain the above copyright notice, license and disclaimer, along with
024: this list of conditions.
025: */
026:
027: package it.stefanochizzolini.clown.documents.contents;
028:
029: import it.stefanochizzolini.clown.documents.Document;
030: import it.stefanochizzolini.clown.documents.contents.colorSpaces.Color;
031: import it.stefanochizzolini.clown.documents.contents.colorSpaces.ColorSpace;
032: import it.stefanochizzolini.clown.documents.contents.colorSpaces.DeviceCMYKColorSpace;
033: import it.stefanochizzolini.clown.documents.contents.colorSpaces.DeviceGrayColor;
034: import it.stefanochizzolini.clown.documents.contents.colorSpaces.DeviceGrayColorSpace;
035: import it.stefanochizzolini.clown.documents.contents.colorSpaces.DeviceRGBColorSpace;
036: import it.stefanochizzolini.clown.documents.contents.fonts.Font;
037: import it.stefanochizzolini.clown.documents.contents.objects.CompositeObject;
038: import it.stefanochizzolini.clown.documents.contents.objects.ContentObject;
039: import it.stefanochizzolini.clown.documents.contents.objects.Operation;
040: import it.stefanochizzolini.clown.documents.contents.objects.ModifyCTM;
041: import it.stefanochizzolini.clown.documents.contents.objects.SetCharSpace;
042: import it.stefanochizzolini.clown.documents.contents.objects.SetFillColor;
043: import it.stefanochizzolini.clown.documents.contents.objects.SetFillColorSpace;
044: import it.stefanochizzolini.clown.documents.contents.objects.SetFont;
045: import it.stefanochizzolini.clown.documents.contents.objects.SetLineCap;
046: import it.stefanochizzolini.clown.documents.contents.objects.SetLineDash;
047: import it.stefanochizzolini.clown.documents.contents.objects.SetLineJoin;
048: import it.stefanochizzolini.clown.documents.contents.objects.SetLineWidth;
049: import it.stefanochizzolini.clown.documents.contents.objects.SetMiterLimit;
050: import it.stefanochizzolini.clown.documents.contents.objects.SetStrokeColor;
051: import it.stefanochizzolini.clown.documents.contents.objects.SetStrokeColorSpace;
052: import it.stefanochizzolini.clown.documents.contents.objects.SetTextLead;
053: import it.stefanochizzolini.clown.documents.contents.objects.SetTextRise;
054: import it.stefanochizzolini.clown.documents.contents.objects.SetTextRenderMode;
055: import it.stefanochizzolini.clown.documents.contents.objects.SetTextScale;
056: import it.stefanochizzolini.clown.documents.contents.objects.SetWordSpace;
057: import it.stefanochizzolini.clown.files.File;
058: import it.stefanochizzolini.clown.objects.PdfDictionary;
059: import it.stefanochizzolini.clown.objects.PdfDirectObject;
060: import it.stefanochizzolini.clown.objects.PdfName;
061: import it.stefanochizzolini.clown.objects.PdfReference;
062: import it.stefanochizzolini.clown.util.NotImplementedException;
063:
064: import java.util.Collection;
065: import java.util.Iterator;
066: import java.util.List;
067: import java.util.ListIterator;
068:
069: /**
070: Content objects scanner.
071: <h3>Remarks</h3>
072: <p>It wraps the content objects collection ({@link Contents}) to scan its graphics state
073: through a forward cursor.</p>
074: <p>Scanning is performed at an arbitrary deepness, according to the content objects nesting:
075: in fact, each deepness level corresponds to a scan level so that at any time it's possible to seamlessly navigate across the levels (see {@link #getParentLevel() }, {@link #getChildLevel() }, {@link #getLeafLevel() })</p>
076:
077: @author Stefano Chizzolini
078: @version 0.0.5
079: @since 0.0.4
080: */
081: public final class ContentScanner {
082: // <class>
083: // <classes>
084: /**
085: Graphics state [PDF:1.6:4.3].
086: */
087: public static final class GraphicsState implements Cloneable {
088: // <class>
089: // <classes>
090: /**
091: Text state [PDF:1.6:5.2].
092: */
093: public static final class TextState implements Cloneable {
094: // <class>
095: // <dynamic>
096: // <fields>
097: /* NOTE: Initialized using prescribed default values. */
098: private double charSpace = 0;
099: private Font font = null;
100: private double fontSize = 0;
101: private double scale = 100;
102: private double lead = 0;
103: private TextRenderModeEnum renderMode = TextRenderModeEnum.Fill;
104: private double rise = 0;
105: private double wordSpace = 0;
106:
107: // </fields>
108:
109: // <interface>
110: // <public>
111: /**
112: Gets a deep copy of the text state object.
113: */
114: /* NOTE: Covariant overriding of Object Object.clone() method. */
115: @Override
116: public TextState clone() {
117: // Shallow copy only.
118: /* NOTE: No mutable object to be cloned. */
119: try {
120: return (TextState) super .clone();
121: } catch (CloneNotSupportedException e) {
122: throw new RuntimeException(e); /* NOTE: It should never happen. */
123: }
124: }
125:
126: /**
127: Gets the current character spacing [PDF:1.6:5.2.1].
128: */
129: public double getCharSpace() {
130: return charSpace;
131: }
132:
133: /**
134: Gets the current font.
135: */
136: public Font getFont() {
137: return font;
138: }
139:
140: /**
141: Gets the current font size.
142: */
143: public double getFontSize() {
144: return fontSize;
145: }
146:
147: /**
148: Gets the current leading [PDF:1.6:5.2.4].
149: */
150: public double getLead() {
151: return lead;
152: }
153:
154: /**
155: Gets the current text rendering mode [PDF:1.6:5.2.5].
156: */
157: public TextRenderModeEnum getRenderMode() {
158: return renderMode;
159: }
160:
161: /**
162: Gets the current text rise [PDF:1.6:5.2.6].
163: */
164: public double getRise() {
165: return rise;
166: }
167:
168: /**
169: Gets the current horizontal scaling [PDF:1.6:5.2.3].
170: */
171: public double getScale() {
172: return scale;
173: }
174:
175: /**
176: Gets the current word spacing [PDF:1.6:5.2.2].
177: */
178: public double getWordSpace() {
179: return wordSpace;
180: }
181: // </public>
182: // </interface>
183: // </dynamic>
184: // </class>
185: }
186:
187: // </classes>
188:
189: // <dynamic>
190: // <fields>
191: /* NOTE: Initialized using prescribed default values. */
192: private double[] ctm = new double[] { 1, 0, 0, 1, 0, 0 };
193: private Color fillColor = DeviceGrayColor.Default;
194: private ColorSpace fillColorSpace = DeviceGrayColorSpace.Default;
195: private LineCapEnum lineCap = LineCapEnum.Butt;
196: private LineDash lineDash = new LineDash();
197: private LineJoinEnum lineJoin = LineJoinEnum.Miter;
198: private double lineWidth = 1;
199: private double miterLimit = 10;
200: private Color strokeColor = DeviceGrayColor.Default;
201: private ColorSpace strokeColorSpace = DeviceGrayColorSpace.Default;
202: private TextState text = new TextState();
203:
204: // </fields>
205:
206: // <interface>
207: // <public>
208: /**
209: Gets a deep copy of the graphics state object.
210: */
211: /* NOTE: Covariant overriding of Object Object.clone() method. */
212: @Override
213: public GraphicsState clone() {
214: GraphicsState clone;
215:
216: // Shallow copy.
217: try {
218: clone = (GraphicsState) super .clone();
219: } catch (CloneNotSupportedException e) {
220: throw new RuntimeException(e); /* NOTE: It should never happen. */
221: }
222:
223: // Deep copy.
224: /* NOTE: Mutable objects are to be cloned. */
225: clone.text = (TextState) text.clone();
226:
227: return clone;
228: }
229:
230: /**
231: Gets the current transformation matrix.
232: */
233: public double[] getCTM() {
234: return ctm;
235: }
236:
237: /**
238: Gets the current color for nonstroking operations [PDF:1.6:4.5.1].
239: */
240: public Color getFillColor() {
241: return fillColor;
242: }
243:
244: /**
245: Gets the current color space for nonstroking operations [PDF:1.6:4.5.1].
246: */
247: public ColorSpace getFillColorSpace() {
248: return fillColorSpace;
249: }
250:
251: /**
252: Gets the current color for stroking operations [PDF:1.6:4.5.1].
253: */
254: public Color getStrokeColor() {
255: return strokeColor;
256: }
257:
258: /**
259: Gets the current color space for stroking operations [PDF:1.6:4.5.1].
260: */
261: public ColorSpace getStrokeColorSpace() {
262: return strokeColorSpace;
263: }
264:
265: /**
266: Gets the current line cap style [PDF:1.6:4.3.2].
267: */
268: public LineCapEnum getLineCap() {
269: return lineCap;
270: }
271:
272: /**
273: Gets the current line dash pattern [PDF:1.6:4.3.2].
274: */
275: public LineDash getLineDash() {
276: return lineDash;
277: }
278:
279: /**
280: Gets the current line join style [PDF:1.6:4.3.2].
281: */
282: public LineJoinEnum getLineJoin() {
283: return lineJoin;
284: }
285:
286: /**
287: Gets the current line width [PDF:1.6:4.3.2].
288: */
289: public double getLineWidth() {
290: return lineWidth;
291: }
292:
293: /**
294: Gets the current miter limit [PDF:1.6:4.3.2].
295: */
296: public double getMiterLimit() {
297: return miterLimit;
298: }
299:
300: /**
301: Gets the current text state.
302: */
303: public TextState getText() {
304: return text;
305: }
306: // </public>
307: // </interface>
308: // </dynamic>
309: // </class>
310: }
311:
312: // </classes>
313:
314: // <dynamic>
315: // <fields>
316: /**
317: Content objects collection.
318: */
319: private Contents contents;
320:
321: /**
322: Current object index at this level.
323: */
324: private int index = 0;
325: /**
326: Current object collection at this level.
327: */
328: private List<ContentObject> objects;
329: /**
330: Current graphics state.
331: */
332: private GraphicsState state;
333:
334: /**
335: Child level.
336: */
337: private ContentScanner childLevel = null;
338: /**
339: Parent level.
340: */
341: private ContentScanner parentLevel = null;
342: /**
343: Operation level.
344: */
345: private ContentScanner leafLevel = this ;
346:
347: // </fields>
348:
349: // <constructors>
350: /**
351: @param contents Content objects collection to scan.
352: */
353: public ContentScanner(Contents contents) {
354: this .objects = this .contents = contents;
355: this .state = new GraphicsState();
356:
357: if (this .objects.size() > 0) {
358: ContentObject currentObject = getCurrent();
359: if (currentObject instanceof CompositeObject) {
360: childLevel = new ContentScanner(this );
361: leafLevel = childLevel.leafLevel;
362: }
363: }
364: }
365:
366: /**
367: @param parentLevel Parent scan level.
368: */
369: private ContentScanner(ContentScanner parentLevel) {
370: this .parentLevel = parentLevel;
371: this .contents = parentLevel.contents;
372: this .objects = (List<ContentObject>) ((CompositeObject) parentLevel
373: .getCurrent()).getObjects();
374: this .state = parentLevel.state.clone();
375:
376: if (this .objects.size() > 0) {
377: ContentObject currentObject = getCurrent();
378: if (currentObject instanceof CompositeObject) {
379: childLevel = new ContentScanner(this );
380: leafLevel = childLevel.leafLevel;
381: }
382: }
383: }
384:
385: // </constructors>
386:
387: // <interface>
388: // <public>
389: /**
390: Gets the current content object within this scan level.
391: @see #getChild()
392: @see #getIndex()
393: @see #getLeaf()
394: @see #getParent()
395: */
396: public ContentObject getCurrent() {
397: try {
398: return objects.get(index);
399: } catch (Exception e) {
400: return null;
401: }
402: }
403:
404: /**
405: Gets the current child object.
406: @see #getCurrent()
407: @see #getLeaf()
408: @see #getParent()
409: */
410: public ContentObject getChild() {
411: return childLevel.getCurrent();
412: }
413:
414: /**
415: Gets the child scan level.
416: @see #getLeafLevel()
417: @see #getParentLevel()
418: */
419: public ContentScanner getChildLevel() {
420: return childLevel;
421: }
422:
423: /**
424: Gets the content context associated to the content objects collection.
425: */
426: public IContentContext getContentContext() {
427: return contents.getContentContext();
428: }
429:
430: /**
431: Gets the contents collection this scanner is inspecting.
432: */
433: public Contents getContents() {
434: return contents;
435: }
436:
437: /**
438: Gets the current position within this scan level.
439: @see #getCurrent()
440: */
441: public int getIndex() {
442: return index;
443: }
444:
445: /**
446: Gets the current leaf object.
447: @see #getCurrent()
448: @see #getChild()
449: @see #getParent()
450: */
451: public ContentObject getLeaf() {
452: return leafLevel.getCurrent();
453: }
454:
455: /**
456: Gets the leaf scan level.
457: @see #getChildLevel()
458: @see #getParentLevel()
459: */
460: public ContentScanner getLeafLevel() {
461: return leafLevel;
462: }
463:
464: /**
465: Gets the current parent object.
466: @see #getCurrent()
467: @see #getChild()
468: @see #getLeaf()
469: */
470: public CompositeObject getParent() {
471: return (CompositeObject) parentLevel.getCurrent();
472: }
473:
474: /**
475: Gets the parent scan level.
476: @see #getChildLevel()
477: @see #getLeafLevel()
478: */
479: public ContentScanner getParentLevel() {
480: return parentLevel;
481: }
482:
483: /**
484: Gets the current graphics state applied to the current content object.
485: <h3>Remarks</h3>
486: <p>The returned object of this method is fundamental for any content manipulation
487: as it represents the actual constraints that affect the current content object rendering.</p>
488: */
489: public GraphicsState getState() {
490: return state;
491: }
492:
493: /**
494: Inserts a content object at the current position.
495: */
496: public void insert(ContentObject object) {
497: objects.add(index, object);
498:
499: // Synchronizing the level state...
500: if (object instanceof Operation) {
501: childLevel = null;
502: leafLevel = this ;
503: } else {
504: childLevel = new ContentScanner(this );
505: leafLevel = childLevel.leafLevel;
506: }
507: }
508:
509: /**
510: Inserts content objects at the current position.
511: <h3>Remarks</h3>
512: <p>After insertion complete, lastly-inserted content object is at the current position.</p>
513: */
514: public void insert(Collection<? extends ContentObject> objects) {
515: int index = 0;
516: int size = objects.size();
517: for (ContentObject object : objects) {
518: insert(object);
519:
520: if (++index < size) {
521: moveNext();
522: }
523: }
524: }
525:
526: /**
527: @version 0.0.5
528: @since 0.0.5
529: */
530: public boolean moveFirst() {
531: index = 0;
532: if (parentLevel == null) {
533: this .state = new GraphicsState();
534: } else {
535: this .state = parentLevel.state.clone();
536: }
537:
538: ContentObject currentObject = null;
539: if (this .objects.size() > 0) {
540: currentObject = getCurrent();
541: if (currentObject instanceof CompositeObject) {
542: childLevel = new ContentScanner(this );
543: leafLevel = childLevel.leafLevel;
544: } else {
545: childLevel = null;
546: leafLevel = null;
547: }
548: }
549:
550: return (currentObject != null);
551: }
552:
553: /**
554: Moves to the next object, possibly recurring within the nested scan levels.
555: @return Whether the next object was successfully reached.
556: */
557: public boolean moveInnerNext() {
558: if (leafLevel == this ) {
559: // Updating the current graphics state...
560: ContentObject currentObject = getCurrent();
561: if (currentObject instanceof Operation) {
562: Operation currentOperation = (Operation) currentObject;
563: if (currentOperation instanceof ModifyCTM) {
564: state.ctm = ((ModifyCTM) currentOperation)
565: .applyTo(state.ctm);
566: } else if (currentOperation instanceof SetCharSpace) {
567: state.text.charSpace = ((SetCharSpace) currentOperation)
568: .getValue();
569: } else if (currentOperation instanceof SetLineCap) {
570: state.lineCap = ((SetLineCap) currentOperation)
571: .getValue();
572: } else if (currentOperation instanceof SetLineDash) {
573: state.lineDash = ((SetLineDash) currentOperation)
574: .getValue();
575: } else if (currentOperation instanceof SetLineJoin) {
576: state.lineJoin = ((SetLineJoin) currentOperation)
577: .getValue();
578: } else if (currentOperation instanceof SetLineWidth) {
579: state.lineWidth = ((SetLineWidth) currentOperation)
580: .getValue();
581: } else if (currentOperation instanceof SetMiterLimit) {
582: state.miterLimit = ((SetMiterLimit) currentOperation)
583: .getValue();
584: } else if (currentOperation instanceof SetTextLead) {
585: state.text.lead = ((SetTextLead) currentOperation)
586: .getValue();
587: } else if (currentOperation instanceof SetTextRise) {
588: state.text.rise = ((SetTextRise) currentOperation)
589: .getValue();
590: } else if (currentOperation instanceof SetTextScale) {
591: state.text.scale = ((SetTextScale) currentOperation)
592: .getValue();
593: } else if (currentOperation instanceof SetTextRenderMode) {
594: state.text.renderMode = ((SetTextRenderMode) currentOperation)
595: .getValue();
596: } else if (currentOperation instanceof SetWordSpace) {
597: state.text.wordSpace = ((SetWordSpace) currentOperation)
598: .getValue();
599: } else if (currentOperation instanceof SetFont) {
600: SetFont setFontOperation = (SetFont) currentOperation;
601: state.text.font = getContentContext()
602: .getResources().getFonts().get(
603: setFontOperation.getName());
604: state.text.fontSize = setFontOperation.getSize();
605: } else if (currentOperation instanceof SetStrokeColor) {
606: state.strokeColor = state.strokeColorSpace
607: .getColor(currentOperation.getOperands()
608: .toArray(new PdfDirectObject[0]));
609: } else if (currentOperation instanceof SetFillColor) {
610: state.fillColor = state.fillColorSpace
611: .getColor(currentOperation.getOperands()
612: .toArray(new PdfDirectObject[0]));
613: } else if (currentOperation instanceof SetStrokeColorSpace) {
614: /*
615: NOTE: The names DeviceGray, DeviceRGB, DeviceCMYK, and Pattern always identify
616: the corresponding color spaces directly; they never refer to resources in the
617: ColorSpace subdictionary [PDF:1.6:4.5.7].
618: */
619: PdfName name = (PdfName) currentOperation
620: .getOperands().get(0);
621: if (name.equals(PdfName.DeviceGray)) {
622: state.strokeColorSpace = DeviceGrayColorSpace.Default;
623: } else if (name.equals(PdfName.DeviceRGB)) {
624: state.strokeColorSpace = DeviceRGBColorSpace.Default;
625: } else if (name.equals(PdfName.DeviceCMYK)) {
626: state.strokeColorSpace = DeviceCMYKColorSpace.Default;
627: }
628: //TODO:special color spaces[PDF:1.6:4.5.5]!!!
629: // else if(name.equals(PdfName.Pattern))
630: // {state.strokeColorSpace = Pattern.Default;}
631: else {
632: state.strokeColorSpace = getContentContext()
633: .getResources().getColorSpaces().get(
634: name);
635: }
636: //TODO:eliminate when full support to color spaces!!!
637: if (state.strokeColorSpace != null) {
638: /*
639: NOTE: The operation also sets the current stroking color
640: to its initial value, which depends on the color space [PDF:1.6:4.5.7].
641: */
642: state.strokeColor = state.strokeColorSpace
643: .getDefaultColor();
644: }
645: } else if (currentOperation instanceof SetFillColorSpace) {
646: /*
647: NOTE: The names DeviceGray, DeviceRGB, DeviceCMYK, and Pattern always identify
648: the corresponding color spaces directly; they never refer to resources in the
649: ColorSpace subdictionary [PDF:1.6:4.5.7].
650: */
651: PdfName name = (PdfName) currentOperation
652: .getOperands().get(0);
653: if (name.equals(PdfName.DeviceGray)) {
654: state.fillColorSpace = DeviceGrayColorSpace.Default;
655: } else if (name.equals(PdfName.DeviceRGB)) {
656: state.fillColorSpace = DeviceRGBColorSpace.Default;
657: } else if (name.equals(PdfName.DeviceCMYK)) {
658: state.fillColorSpace = DeviceCMYKColorSpace.Default;
659: }
660: //TODO:special color spaces[PDF:1.6:4.5.5]!!!
661: // else if(name.equals(PdfName.Pattern))
662: // {state.fillColorSpace = Pattern.Default;}
663: else {
664: state.fillColorSpace = getContentContext()
665: .getResources().getColorSpaces().get(
666: name);
667: }
668: //TODO:eliminate when full support to color spaces!!!
669: if (state.fillColorSpace != null) {
670: /*
671: NOTE: The operation also sets the current nonstroking color
672: to its initial value, which depends on the color space [PDF:1.6:4.5.7].
673: */
674: state.fillColor = state.fillColorSpace
675: .getDefaultColor();
676: }
677: }
678: }
679: }
680:
681: // Has the child level run out of objects?
682: if (childLevel == null || !childLevel.moveInnerNext()) // No more objects available at child level.
683: {
684: // Trying to move to the next object...
685: if (index < objects.size()) {
686: index++;
687: // Has the objects collection run out of objects?
688: if (index == objects.size()) {
689: childLevel = null;
690: leafLevel = this ;
691:
692: return false;
693: }
694: } else {
695: return false;
696: }
697:
698: if (getCurrent() instanceof Operation) // Operation.
699: {
700: childLevel = null;
701: leafLevel = this ;
702: } else // Composite object.
703: {
704: childLevel = new ContentScanner(this );
705: leafLevel = childLevel.leafLevel;
706: }
707: } else // Objects available at child level.
708: {
709: leafLevel = childLevel.leafLevel;
710: }
711:
712: return true;
713: }
714:
715: /**
716: @version 0.0.5
717: @since 0.0.5
718: */
719: public boolean moveLast() {
720: while (moveNext()) {
721: }
722: return false;
723: }
724:
725: /**
726: Moves to the next object within this scan level.
727: @return Whether the next object was successfully reached.
728: */
729: public boolean moveNext() {
730: //Moving to the next object...
731: int currentObjectIndex = index;
732: while (currentObjectIndex == index) {
733: // Run out of objects?
734: if (!moveInnerNext())
735: return false;
736: }
737:
738: return true;
739: }
740:
741: /**
742: Removes the content object at the current position.
743: */
744: public ContentObject remove() {
745: throw new NotImplementedException();
746: }
747:
748: /**
749: Replaces the content object at the current position.
750: */
751: public ContentObject setCurrent(ContentObject object) {
752: throw new NotImplementedException();
753: }
754: // </public>
755: // </interface>
756: // </dynamic>
757: // </class>
758: }
|