001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.quercus.lib.pdf;
031:
032: import com.caucho.quercus.annotation.NotNull;
033: import com.caucho.quercus.annotation.Optional;
034: import com.caucho.quercus.env.BooleanValue;
035: import com.caucho.quercus.env.Env;
036: import com.caucho.quercus.env.TempBufferBytesValue;
037: import com.caucho.quercus.env.Value;
038: import com.caucho.quercus.env.StringValue;
039: import com.caucho.util.L10N;
040: import com.caucho.vfs.Path;
041: import com.caucho.vfs.TempStream;
042: import com.caucho.vfs.TempBuffer;
043: import com.caucho.vfs.WriteStream;
044:
045: import java.io.IOException;
046: import java.util.ArrayList;
047: import java.util.HashMap;
048: import java.util.logging.Logger;
049:
050: /**
051: * pdf object oriented API facade
052: */
053: public class PDF {
054: private static final Logger log = Logger.getLogger(PDF.class
055: .getName());
056: private static final L10N L = new L10N(PDF.class);
057:
058: private static final double KAPPA = 0.5522847498;
059:
060: private static final int PAGE_GROUP = 8;
061:
062: private static HashMap<String, Font> _faceMap = new HashMap<String, Font>();
063:
064: private HashMap<PDFFont, PDFFont> _fontMap = new HashMap<PDFFont, PDFFont>();
065:
066: private HashMap<PDFProcSet, PDFProcSet> _procSetMap = new HashMap<PDFProcSet, PDFProcSet>();
067:
068: private TempStream _tempStream;
069: private WriteStream _os;
070: private PDFWriter _out;
071:
072: private ArrayList<PDFPage> _pageGroup = new ArrayList<PDFPage>();
073: private ArrayList<Integer> _pagesGroupList = new ArrayList<Integer>();
074: private int _pageCount;
075:
076: private int _catalogId;
077:
078: private int _pageParentId;
079:
080: private PDFPage _page;
081: private PDFStream _stream;
082:
083: public PDF(Env env) {
084: _out = new PDFWriter(env.getOut());
085: }
086:
087: public boolean begin_document(@Optional
088: String fileName, @Optional
089: String optList) throws IOException {
090: _tempStream = new TempStream();
091: _tempStream.openWrite();
092: _os = new WriteStream(_tempStream);
093:
094: _out = new PDFWriter(_os);
095: _out.beginDocument();
096:
097: _catalogId = _out.allocateId(1);
098: _pageParentId = _out.allocateId(1);
099:
100: return true;
101: }
102:
103: public boolean begin_page(double width, double height)
104: throws IOException {
105: if (PAGE_GROUP <= _pageGroup.size()) {
106: _out.writePageGroup(_pageParentId, _pageGroup);
107: _pageGroup.clear();
108:
109: _pagesGroupList.add(_pageParentId);
110: _pageParentId = _out.allocateId(1);
111: }
112:
113: _page = new PDFPage(_out, _pageParentId, width, height);
114: _stream = _page.getStream();
115:
116: _pageCount++;
117:
118: _pageGroup.add(_page);
119:
120: return true;
121: }
122:
123: public boolean begin_page_ext(double width, double height,
124: String opt) throws IOException {
125: return begin_page(width, height);
126: }
127:
128: public boolean set_info(String key, String value) {
129: if ("Author".equals(key)) {
130: _out.setAuthor(key);
131: return true;
132: } else if ("Title".equals(key)) {
133: _out.setTitle(key);
134: return true;
135: } else if ("Creator".equals(key)) {
136: _out.setCreator(key);
137: return true;
138: } else
139: return false;
140: }
141:
142: public boolean set_parameter(String key, String value) {
143: return false;
144: }
145:
146: public boolean set_value(String key, double value) {
147: return false;
148: }
149:
150: /**
151: * Returns the result as a string.
152: */
153: public Value get_buffer(Env env) {
154: TempStream ts = _tempStream;
155: _tempStream = null;
156:
157: if (ts == null)
158: return BooleanValue.FALSE;
159:
160: StringValue result = env.createBinaryBuilder();
161: for (TempBuffer ptr = ts.getHead(); ptr != null; ptr = ptr
162: .getNext()) {
163: result.append(ptr.getBuffer(), 0, ptr.getLength());
164: }
165:
166: ts.destroy();
167:
168: return result;
169: }
170:
171: /**
172: * Returns the error message.
173: */
174: public String get_errmsg() {
175: return "";
176: }
177:
178: /**
179: * Returns the error number.
180: */
181: public int get_errnum() {
182: return 0;
183: }
184:
185: /**
186: * Returns the value for a parameter.
187: */
188: public String get_parameter(String name, @Optional
189: double modifier) {
190: if ("fontname".equals(name)) {
191: PDFFont font = _stream.getFont();
192:
193: if (font != null)
194: return font.getFontName();
195: else
196: return null;
197: } else
198: return null;
199: }
200:
201: /**
202: * Returns the value for a parameter.
203: */
204: public double get_value(String name, @Optional
205: double modifier) {
206: if ("ascender".equals(name)) {
207: PDFFont font = _stream.getFont();
208:
209: if (font != null)
210: return font.getAscender();
211: else
212: return 0;
213: } else if ("capheight".equals(name)) {
214: PDFFont font = _stream.getFont();
215:
216: if (font != null)
217: return font.getCapHeight();
218: else
219: return 0;
220: } else if ("descender".equals(name)) {
221: PDFFont font = _stream.getFont();
222:
223: if (font != null)
224: return font.getDescender();
225: else
226: return 0;
227: } else if ("fontsize".equals(name)) {
228: return _stream.getFontSize();
229: } else
230: return 0;
231: }
232:
233: public boolean initgraphics(Env env) {
234: env.stub("initgraphics");
235:
236: return false;
237: }
238:
239: /**
240: * Loads a font for later use.
241: *
242: * @param name the font name, e.g. Helvetica
243: * @param encoding the font encoding, e.g. winansi
244: * @param opt any options
245: */
246: public PDFFont load_font(String name, String encoding, String opt)
247: throws IOException {
248: Font face = loadFont(name);
249:
250: PDFFont font = new PDFFont(face, encoding, opt);
251:
252: PDFFont oldFont = _fontMap.get(font);
253:
254: if (oldFont != null)
255: return oldFont;
256:
257: font.setId(_out.allocateId(1));
258:
259: _fontMap.put(font, font);
260:
261: _out.addPendingObject(font);
262:
263: return font;
264: }
265:
266: private Font loadFont(String name) throws IOException {
267: synchronized (_faceMap) {
268: Font face = _faceMap.get(name);
269:
270: if (face == null) {
271: face = new AfmParser().parse(name);
272:
273: _faceMap.put(name, face);
274: }
275:
276: return face;
277: }
278: }
279:
280: /**
281: * Sets the dashing
282: *
283: * @param b black length
284: * @param w which length
285: */
286: public boolean setdash(double b, double w) {
287: _stream.setDash(b, w);
288:
289: return true;
290: }
291:
292: /**
293: * Sets the dashing
294: */
295: public boolean setdashpattern(Env env, @Optional
296: String optlist) {
297: env.stub("setdashpattern");
298:
299: return false;
300: }
301:
302: /**
303: * Sets the flatness
304: */
305: public boolean setflat(Env env, double flatness) {
306: env.stub("setflat");
307:
308: return false;
309: }
310:
311: /**
312: * Sets the linecap style
313: */
314: public boolean setlinecap(Env env, int cap) {
315: env.stub("setlinecap");
316:
317: return false;
318: }
319:
320: /**
321: * Sets the linejoin style
322: */
323: public boolean setlinejoin(Env env, int linejoin) {
324: env.stub("setlinejoin");
325:
326: return false;
327: }
328:
329: /**
330: * Sets the current font
331: *
332: * @param name the font name, e.g. Helvetica
333: * @param encoding the font encoding, e.g. winansi
334: * @param opt any options
335: */
336: public boolean setfont(@NotNull
337: PDFFont font, double size) throws IOException {
338: if (font == null)
339: return false;
340:
341: _stream.setFont(font, size);
342:
343: _page.addResource(font.getResource());
344:
345: return true;
346: }
347:
348: /**
349: * Sets the matrix style
350: */
351: public boolean setmatrix(Env env, double a, double b, double c,
352: double d, double e, double f) {
353: env.stub("setmatrix");
354:
355: return false;
356: }
357:
358: /**
359: * Sets the miter limit
360: */
361: public boolean setmiterlimit(Env env, double v) {
362: env.stub("setmiterlimit");
363:
364: return false;
365: }
366:
367: /**
368: * Sets the shading pattern
369: */
370: public boolean shading_pattern(Env env, int shading, @Optional
371: String optlist) {
372: env.stub("shading_pattern");
373:
374: return false;
375: }
376:
377: /**
378: * Define a blend
379: */
380: public int shading(Env env, String type, double x1, double y1,
381: double x2, double y2, double c1, double c2, double c3,
382: double c4, @Optional
383: String optlist) {
384: env.stub("shading");
385:
386: return 0;
387: }
388:
389: /**
390: * Fill with a shading object.
391: */
392: public boolean shfill(Env env, int shading) {
393: env.stub("shfill");
394:
395: return false;
396: }
397:
398: /**
399: * Returns the length of a string for a font.
400: */
401: public double stringwidth(String string, @NotNull
402: PDFFont font, double size) {
403: if (font == null)
404: return 0;
405:
406: return size * font.stringWidth(string) / 1000.0;
407: }
408:
409: /**
410: * Sets the text position.
411: */
412: public boolean set_text_pos(double x, double y) {
413: _stream.setTextPos(x, y);
414:
415: return true;
416: }
417:
418: /**
419: * Fills
420: */
421: public boolean fill() {
422: _stream.fill();
423:
424: return true;
425: }
426:
427: /**
428: * Closes the path
429: */
430: public boolean closepath() {
431: _stream.closepath();
432:
433: return true;
434: }
435:
436: /**
437: * Appends the current path to the clipping path.
438: */
439: public boolean clip() {
440: _stream.clip();
441:
442: return true;
443: }
444:
445: /**
446: * Closes the path strokes
447: */
448: public boolean closepath_stroke() {
449: _stream.closepathStroke();
450:
451: return true;
452: }
453:
454: /**
455: * Closes the path strokes
456: */
457: public boolean closepath_fill_stroke() {
458: _stream.closepathFillStroke();
459:
460: return true;
461: }
462:
463: /**
464: * Fills
465: */
466: public boolean fill_stroke() {
467: _stream.fillStroke();
468:
469: return true;
470: }
471:
472: /**
473: * Ends the path
474: */
475: public boolean endpath() {
476: _stream.endpath();
477:
478: return true;
479: }
480:
481: /**
482: * Draws a bezier curve
483: */
484: public boolean curveto(double x1, double y1, double x2, double y2,
485: double x3, double y3) {
486: _stream.curveTo(x1, y1, x2, y2, x3, y3);
487:
488: return true;
489: }
490:
491: /**
492: * Draws a bezier curve
493: */
494: public boolean curveto_b(double x1, double y1, double x2, double y2) {
495: _stream.curveTo(x1, y1, x1, y1, x2, y2);
496:
497: return true;
498: }
499:
500: /**
501: * Draws a bezier curve
502: */
503: public boolean curveto_e(double x1, double y1, double x2, double y2) {
504: _stream.curveTo(x1, y1, x2, y2, x2, y2);
505:
506: return true;
507: }
508:
509: /**
510: * Creates a counterclockwise arg
511: */
512: public boolean arc(double x1, double y1, double r, double a,
513: double b) {
514: a = a % 360;
515: if (a < 0)
516: a += 360;
517:
518: b = b % 360;
519: if (b < 0)
520: b += 360;
521:
522: if (b < a)
523: b += 360;
524:
525: int aQuarter = (int) (a / 90);
526: int bQuarter = (int) (b / 90);
527:
528: if (aQuarter == bQuarter) {
529: clockwiseArc(x1, y1, r, a, b);
530: } else {
531: clockwiseArc(x1, y1, r, a, (aQuarter + 1) * 90);
532:
533: for (int q = aQuarter + 1; q < bQuarter; q++)
534: clockwiseArc(x1, y1, r, q * 90, (q + 1) * 90);
535:
536: clockwiseArc(x1, y1, r, bQuarter * 90, b);
537: }
538:
539: return true;
540: }
541:
542: /**
543: * Creates a clockwise arc
544: */
545: public boolean arcn(double x1, double y1, double r, double a,
546: double b) {
547: a = a % 360;
548: if (a < 0)
549: a += 360;
550:
551: b = b % 360;
552: if (b < 0)
553: b += 360;
554:
555: if (a < b)
556: a += 360;
557:
558: int aQuarter = (int) (a / 90);
559: int bQuarter = (int) (b / 90);
560:
561: if (aQuarter == bQuarter) {
562: counterClockwiseArc(x1, y1, r, a, b);
563: } else {
564: counterClockwiseArc(x1, y1, r, a, aQuarter * 90);
565:
566: for (int q = aQuarter - 1; bQuarter < q; q--)
567: counterClockwiseArc(x1, y1, r, (q + 1) * 90, q * 90);
568:
569: counterClockwiseArc(x1, y1, r, (bQuarter + 1) * 90, b);
570: }
571:
572: return true;
573: }
574:
575: /**
576: * Creates an arc from 0 to pi/2
577: */
578: private boolean clockwiseArc(double x, double y, double r,
579: double aDeg, double bDeg) {
580: double a = aDeg * Math.PI / 180.0;
581: double b = bDeg * Math.PI / 180.0;
582:
583: double cos_a = Math.cos(a);
584: double sin_a = Math.sin(a);
585:
586: double x1 = x + r * cos_a;
587: double y1 = y + r * sin_a;
588:
589: double cos_b = Math.cos(b);
590: double sin_b = Math.sin(b);
591:
592: double x2 = x + r * cos_b;
593: double y2 = y + r * sin_b;
594:
595: double l = KAPPA * r * 2 * (b - a) / Math.PI;
596:
597: lineto(x1, y1);
598: curveto(x1 - l * sin_a, y1 + l * cos_a, x2 + l * sin_b, y2 - l
599: * cos_b, x2, y2);
600:
601: return true;
602: }
603:
604: /**
605: * Creates an arc from 0 to pi/2
606: */
607: private boolean counterClockwiseArc(double x, double y, double r,
608: double aDeg, double bDeg) {
609: double a = aDeg * Math.PI / 180.0;
610: double b = bDeg * Math.PI / 180.0;
611:
612: double cos_a = Math.cos(a);
613: double sin_a = Math.sin(a);
614:
615: double x1 = x + r * cos_a;
616: double y1 = y + r * sin_a;
617:
618: double cos_b = Math.cos(b);
619: double sin_b = Math.sin(b);
620:
621: double x2 = x + r * cos_b;
622: double y2 = y + r * sin_b;
623:
624: double l = KAPPA * r * 2 * (a - b) / Math.PI;
625:
626: lineto(x1, y1);
627: curveto(x1 + l * sin_a, y1 - l * cos_a, x2 - l * sin_b, y2 + l
628: * cos_b, x2, y2);
629:
630: return true;
631: }
632:
633: /**
634: * Creates a circle
635: */
636: public boolean circle(double x1, double y1, double r) {
637: double l = r * KAPPA;
638:
639: moveto(x1, y1 + r);
640:
641: curveto(x1 - l, y1 + r, x1 - r, y1 + l, x1 - r, y1);
642:
643: curveto(x1 - r, y1 - l, x1 - l, y1 - r, x1, y1 - r);
644:
645: curveto(x1 + l, y1 - r, x1 + r, y1 - l, x1 + r, y1);
646:
647: curveto(x1 + r, y1 + l, x1 + l, y1 + r, x1, y1 + r);
648:
649: return true;
650: }
651:
652: /**
653: * Sets the graphics position.
654: */
655: public boolean lineto(double x, double y) {
656: _stream.lineTo(x, y);
657:
658: return true;
659: }
660:
661: /**
662: * Sets the graphics position.
663: */
664: public boolean moveto(double x, double y) {
665: _stream.moveTo(x, y);
666:
667: return true;
668: }
669:
670: /**
671: * Creates a rectangle
672: */
673: public boolean rect(double x, double y, double width, double height) {
674: _stream.rect(x, y, width, height);
675:
676: return true;
677: }
678:
679: /**
680: * Sets the color to a grayscale
681: */
682: public boolean setgray_stroke(double g) {
683: return _stream.setcolor("stroke", "gray", g, 0, 0, 0);
684: }
685:
686: /**
687: * Sets the color to a grayscale
688: */
689: public boolean setgray_fill(double g) {
690: return _stream.setcolor("fill", "gray", g, 0, 0, 0);
691: }
692:
693: /**
694: * Sets the color to a grayscale
695: */
696: public boolean setgray(double g) {
697: return _stream.setcolor("both", "gray", g, 0, 0, 0);
698: }
699:
700: /**
701: * Sets the color to a rgb
702: */
703: public boolean setrgbcolor_stroke(double r, double g, double b) {
704: return _stream.setcolor("stroke", "rgb", r, g, b, 0);
705: }
706:
707: /**
708: * Sets the fill color to a rgb
709: */
710: public boolean setrgbcolor_fill(double r, double g, double b) {
711: return _stream.setcolor("fill", "rgb", r, g, b, 0);
712: }
713:
714: /**
715: * Sets the color to a rgb
716: */
717: public boolean setrgbcolor(double r, double g, double b) {
718: return _stream.setcolor("both", "rgb", r, g, b, 0);
719: }
720:
721: /**
722: * Sets the color
723: */
724: public boolean setcolor(String fstype, String colorspace,
725: double c1, @Optional
726: double c2, @Optional
727: double c3, @Optional
728: double c4) {
729: return _stream.setcolor(fstype, colorspace, c1, c2, c3, c4);
730: }
731:
732: /**
733: * Sets the line width
734: */
735: public boolean setlinewidth(double w) {
736: return _stream.setlinewidth(w);
737: }
738:
739: /**
740: * Concatenates the matrix
741: */
742: public boolean concat(double a, double b, double c, double d,
743: double e, double f) {
744: return _stream.concat(a, b, c, d, e, f);
745: }
746:
747: /**
748: * open image
749: */
750: public PDFImage open_image_file(String type, Path file, @Optional
751: String stringParam, @Optional
752: int intParam) throws IOException {
753: PDFImage img = new PDFImage(file);
754:
755: img.setId(_out.allocateId(1));
756:
757: _out.addPendingObject(img);
758:
759: return img;
760: }
761:
762: /**
763: * open image
764: */
765: public PDFImage load_image(String type, Path file, @Optional
766: String optlist) throws IOException {
767: PDFImage img = new PDFImage(file);
768:
769: img.setId(_out.allocateId(1));
770:
771: _out.addPendingObject(img);
772:
773: return img;
774: }
775:
776: public boolean fit_image(PDFImage img, double x, double y,
777: @Optional
778: String opt) {
779: _page.addResource(img.getResource());
780:
781: _stream.save();
782:
783: concat(img.get_width(), 0, 0, img.get_height(), x, y);
784:
785: _stream.fit_image(img);
786:
787: _stream.restore();
788:
789: return true;
790: }
791:
792: /**
793: * Skews the coordinates
794: *
795: * @param a degrees to skew the x axis
796: * @param b degrees to skew the y axis
797: */
798: public boolean skew(double aDeg, double bDeg) {
799: double a = aDeg * Math.PI / 180;
800: double b = bDeg * Math.PI / 180;
801:
802: return _stream.concat(1, Math.tan(a), Math.tan(b), 1, 0, 0);
803: }
804:
805: /**
806: * scales the coordinates
807: *
808: * @param sx amount to scale the x axis
809: * @param sy amount to scale the y axis
810: */
811: public boolean scale(double sx, double sy) {
812: return _stream.concat(sx, 0, 0, sy, 0, 0);
813: }
814:
815: /**
816: * translates the coordinates
817: *
818: * @param tx amount to translate the x axis
819: * @param ty amount to translate the y axis
820: */
821: public boolean translate(double tx, double ty) {
822: return _stream.concat(1, 0, 0, 1, tx, ty);
823: }
824:
825: /**
826: * rotates the coordinates
827: *
828: * @param p amount to rotate
829: */
830: public boolean rotate(double pDeg) {
831: double p = pDeg * Math.PI / 180;
832:
833: return _stream.concat(Math.cos(p), Math.sin(p), -Math.sin(p),
834: Math.cos(p), 0, 0);
835: }
836:
837: /**
838: * Saves the graphics state.
839: */
840: public boolean save() {
841: return _stream.save();
842: }
843:
844: /**
845: * Restores the graphics state.
846: */
847: public boolean restore() {
848: return _stream.restore();
849: }
850:
851: /**
852: * Displays text
853: */
854: public boolean show(String text) {
855: _stream.show(text);
856:
857: return true;
858: }
859:
860: /**
861: * Displays text
862: */
863: public boolean show_boxed(String text, double x, double y,
864: double width, double height, String mode, @Optional
865: String feature) {
866: set_text_pos(x, y);
867: _stream.show(text);
868:
869: return true;
870: }
871:
872: /**
873: * Displays text
874: */
875: public boolean show_xy(String text, double x, double y) {
876: set_text_pos(x, y);
877: _stream.show(text);
878:
879: return true;
880: }
881:
882: /**
883: * Draws the graph
884: */
885: public boolean stroke() {
886: _stream.stroke();
887:
888: return true;
889: }
890:
891: /**
892: * Displays text
893: */
894: public boolean continue_text(String text) {
895: _stream.continue_text(text);
896:
897: return true;
898: }
899:
900: public boolean end_page() {
901: _stream.flush();
902:
903: PDFProcSet procSet = _stream.getProcSet();
904:
905: _page.addResource(procSet.getResource());
906:
907: _page = null;
908: _stream = null;
909:
910: return true;
911: }
912:
913: public boolean end_page_ext(String optlist) {
914: return end_page();
915: }
916:
917: public boolean end_document(@Optional
918: String optList) throws IOException {
919: if (_pageGroup.size() > 0) {
920: _out.writePageGroup(_pageParentId, _pageGroup);
921: _pageGroup.clear();
922:
923: if (_pagesGroupList.size() > 0)
924: _pagesGroupList.add(_pageParentId);
925: }
926:
927: _out.writeCatalog(_catalogId, _pageParentId, _pagesGroupList,
928: _pageCount);
929:
930: _out.endDocument();
931:
932: _os.close();
933: _out = null;
934:
935: return true;
936: }
937:
938: public boolean close() throws IOException {
939: return end_document("");
940: }
941:
942: public boolean delete() throws IOException {
943: return true;
944: }
945:
946: public String toString() {
947: return "PDF[]";
948: }
949: }
|