001: //The contents of this file are subject to the Mozilla Public License Version 1.1
002: //(the "License"); you may not use this file except in compliance with the
003: //License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
004: //
005: //Software distributed under the License is distributed on an "AS IS" basis,
006: //WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
007: //for the specific language governing rights and
008: //limitations under the License.
009: //
010: //The Original Code is "The Columba Project"
011: //
012: //The Initial Developers of the Original Code are Frederik Dietz and Timo Stich.
013: //Portions created by Frederik Dietz and Timo Stich are Copyright (C) 2003.
014: //
015: //All Rights Reserved.
016: //
017: package org.columba.core.print;
018:
019: import java.awt.Graphics2D;
020: import java.awt.Rectangle;
021: import java.awt.Shape;
022: import java.awt.geom.Point2D;
023: import java.io.IOException;
024: import java.net.URL;
025: import java.util.logging.Logger;
026:
027: import javax.swing.JTextPane;
028: import javax.swing.text.Document;
029: import javax.swing.text.View;
030: import javax.swing.text.html.HTMLDocument;
031: import javax.swing.text.html.HTMLEditorKit;
032:
033: /**
034: * Class for representing a HTML print object. Objects of this
035: * type is intended for inclusion in cDocument objects for printing.
036: * Division into multiple pages represented by cPage is supported.
037: *
038: * @author Karl Peder Olesen (karlpeder), 20030601
039: *
040: */
041: public class cHTMLPart extends cPrintObject {
042:
043: private static final Logger LOG = Logger
044: .getLogger("org.columba.core.print");
045:
046: /** IContainer holding the HTML to be printed (used to control layout etc. */
047: private JTextPane mPane = null;
048:
049: /** Y-coordinate in mPane to start printing at */
050: private cUnit mStartY = new cCmUnit(0.0);
051:
052: /** Flag indicating whether scaling of the print is allowed (default is no) */
053: private boolean mScaleAllowed;
054:
055: /**
056: * Creates a new empty HTML print object.
057: * As default, scaling the print to fit is not allowed
058: */
059: public cHTMLPart() {
060: this (false);
061: }
062:
063: /**
064: * Creates a new empty HTML print object and sets whether scaling is allowed
065: * @param scaleAllowed If true, the print is allowed to "scale to fit"
066: */
067: public cHTMLPart(boolean scaleAllowed) {
068: super ();
069: mScaleAllowed = scaleAllowed;
070: }
071:
072: /**
073: * Sets the HTML document to be printed.
074: * @param html HTML document to be printed
075: */
076: public void setHTML(HTMLDocument html) {
077: mPane = new JTextPane();
078: mPane.setDoubleBuffered(false);
079: mPane.setContentType("text/html");
080: mPane.setDocument(html); // "store" html in jTextPane container
081: mStartY = new cCmUnit(0.0); // reset starting position in y-direction
082: }
083:
084: /**
085: * Sets the HTML document to be printed.
086: * Precondition: The URL given contains a HTML document
087: * @param url url to file with HTML document
088: * @throws IOException if errors occur while reading HTML document from file
089: */
090: public void setHTML(URL url) throws IOException {
091: /*
092: * By using an instance of SyncHTMLEditorKit, the html should load
093: * synchroniously - so everything is loaded before printing starts
094: */
095: mPane = new JTextPane();
096: mPane.setDoubleBuffered(false);
097: mPane.setEditorKit(new SyncHTMLEditorKit());
098: mPane.setContentType("text/html");
099: mPane.setPage(url);
100: mStartY = new cCmUnit(0.0); // reset starting position in y-direction
101: }
102:
103: /**
104: * Sets the starting position in y-direction. A starting position != 0 is used
105: * when printing anything but the first page. This bookkeeping is usually done
106: * internally when creating pagebreaks using the method breakBlock.
107: * Therefore this method has not been made public.
108: * @param y Starting position in y-direction
109: */
110: protected void setStartY(cUnit y) {
111: mStartY = new cCmUnit(y);
112: }
113:
114: /**
115: * Prints the contents of this HTML print object using the supplied
116: * Graphics2D object.
117: * @param g Used for rendering (i.e. printing) this HTML print object
118: * @see org.columba.core.print.cPrintObject#print(java.awt.Graphics2D)
119: */
120: public void print(Graphics2D g) {
121: /*
122: * *20030609, karlpeder* Introduced scaling
123: */
124: computePositionAndSize();
125:
126: // get origin & size information (height as "total" height minus current pos.)
127: cPoint origin = getDrawingOrigin();
128: double width = getDrawingSize().getWidth().getPoints();
129: double height = getPage().getPrintableAreaSize().getHeight()
130: .sub(getLocation().getY()).getPoints();
131:
132: /*
133: * TODO (@author karlpeder): Guess that right thing to do is to get height as getDrawingSize().getHeight(),
134: * since this should take top- and bottom margin of this print
135: * object into account. But the height seems not to be set
136: * correctly in computePositionAndSize() (*20030604, karlpeder*)
137: */
138: // set size of mPane according to the available width
139: // and fetch root view
140: mPane.setSize((int) width, Integer.MAX_VALUE);
141: mPane.validate();
142:
143: View rootView = mPane.getUI().getRootView(mPane);
144:
145: // scale the graphics
146: double scale = scaleFactor(new cPointUnit(width));
147: g.scale(scale, scale);
148:
149: // set clipping for the graphics object
150: Shape oldClip = g.getClip();
151: g.setClip((int) (origin.getX().getPoints() / scale),
152: (int) (origin.getY().getPoints() / scale),
153: (int) (width / scale), (int) (height / scale));
154:
155: // translate g to line up with origin of print area (trans 1)
156: Point2D.Double trans = new Point2D.Double(g.getClipBounds()
157: .getX(), g.getClipBounds().getY());
158: g.translate(trans.getX(), trans.getY());
159:
160: // set allocation (defines print area together with the clipping
161: // and translation made above), and print...
162: Rectangle allocation = new Rectangle(0, (int) -mStartY
163: .getPoints(), (int) mPane.getMinimumSize().getWidth(),
164: (int) mPane.getPreferredSize().getHeight());
165: printView(g, rootView, allocation, height / scale);
166:
167: // translate graphics object back to original position and reset clip and scaling
168: g.translate(-trans.getX(), -trans.getY());
169: g.scale(1 / scale, 1 / scale);
170: g.setClip(oldClip);
171: }
172:
173: /**
174: * Private utility to print a view (called from the print method).<br>
175: * The traversal through views and their children is the same as in
176: * calcBreakHeightForView.
177: *
178: * @param g Graphics object to print on
179: * @param view The View object to operate on
180: * @param allocation Allocation for the view (where to render)
181: * @param maxHeight Views starting after maxHeight is not printed
182: */
183: private void printView(Graphics2D g, View view, Shape allocation,
184: double maxHeight) {
185: if (view.getViewCount() > 0) {
186: // child views exist - operate recursively on these
187: Shape childAllocation;
188: View childView;
189:
190: for (int i = 0; i < view.getViewCount(); i++) {
191: childAllocation = view
192: .getChildAllocation(i, allocation);
193:
194: if (childAllocation != null) {
195: childView = view.getView(i);
196:
197: // handle child view by recursive call
198: printView(g, childView, childAllocation, maxHeight);
199: }
200: }
201: } else {
202: // no childs - we have a leaf view (i.e. with contents)
203: double viewStartY = allocation.getBounds().getY();
204:
205: if ((viewStartY >= 0) && (viewStartY < maxHeight)) {
206: // view starts on page - print it
207: view.paint(g, allocation);
208: }
209: }
210: }
211:
212: /**
213: * Returns the size of this HTML print object subject to the
214: * given width.<br>
215: * If scaling is allowed, and the contents can not be fitted inside
216: * the given width, the content is scaled to fit before the size is
217: * returned, i.e. the scaled size is returned.<br>
218: * NB: The height returned will always be from the starting point
219: * (which could be different from the top) to the end of the current
220: * content, independent on whether everything will or can be printed
221: * on onto one page.
222: *
223: * @param maxWidth Max. allowable width this print object can occupy
224: *
225: * @see org.columba.core.print.cPrintObject#getSize(org.columba.core.print.cUnit)
226: */
227: public cSize getSize(cUnit maxWidth) {
228: /*
229: * *20030609, karlpeder* Introduced scaling
230: */
231:
232: // resize jTextPane component to calculate height and get it
233: double width = maxWidth.sub(leftMargin).sub(rightMargin)
234: .getPoints();
235: mPane.setSize((int) width, Integer.MAX_VALUE);
236: mPane.validate();
237:
238: double height = mPane.getPreferredSize().getHeight();
239:
240: // correct for starting position if printing should not start at the top
241: height = height - mStartY.getPoints();
242:
243: // calculate size and return it
244: double scale = scaleFactor(new cPointUnit(width));
245: cUnit w = new cCmUnit(maxWidth); // width unchanged
246: cUnit h = new cCmUnit();
247: h.setPoints(height); // height of content
248: h.addI(topMargin); // + top margin
249: h.addI(bottomMargin); // + bottom margin
250: h.setPoints(h.getPoints() * scale); // height corrected for scaling
251:
252: return new cSize(w, h);
253: }
254:
255: /**
256: * Returns the scale, which should be applied to the content to make it
257: * fit inside the given width.<br>
258: * If scaling is not allowed, 1.0 will be returned.<br>
259: * If the content fits inside the given width, or is smaller, 1.0 will
260: * be returned.
261: * @author Karl Peder Olesen (karlpeder), 20030609
262: * @param maxWidth Max. allowable width this print object can occupy
263: * @return scale to be applied to make the contents fit inside the given width
264: */
265: private double scaleFactor(cUnit maxWidth) {
266: mPane.validate(); // ensure contents is layed out properly
267:
268: if (!mScaleAllowed) {
269: LOG.info("Scaling not active - returning scale=1.0");
270:
271: return 1.0;
272: } else {
273: // calculate scaling and return it
274: double width = maxWidth.sub(leftMargin).sub(rightMargin)
275: .getPoints();
276: double scale;
277:
278: if (mPane.getMinimumSize().getWidth() > width) {
279: scale = width / mPane.getMinimumSize().getWidth();
280: } else {
281: scale = 1.0; // do not scale up, i.e. no scale factor above 1.0
282: }
283:
284: LOG.info("Returning scale=" + scale);
285:
286: return scale;
287: }
288: }
289:
290: /**
291: * Divides (breaks) this HTML print object into a remainder (which fits
292: * inside the given max height) and the rest. The remainder is returned and
293: * "the rest" is stored by modifying this object.
294: *
295: * @param w Max. allowable width this print object can occupy
296: * @param maxHeight Max. allowable height before breaking
297: * @return The part of the print object, which fits inside the given max height
298: *
299: * @see org.columba.core.print.cPrintObject#breakBlock(org.columba.core.print.cUnit, org.columba.core.print.cUnit)
300: */
301: public cPrintObject breakBlock(cUnit w, cUnit maxHeight) {
302: /*
303: * *20030609, karlpeder* Introduced scaling
304: */
305:
306: // get size of content (width, height is size without scaling)
307: cSize contentSize = this .getSize(w); // scaled size
308: double scale = scaleFactor(w);
309: int width = (int) (contentSize.getWidth().getPoints() / scale);
310: int height = (int) (contentSize.getHeight().getPoints() / scale);
311: int startY = (int) mStartY.getPoints();
312:
313: // define allocation rectangle (startY is used to compensate for
314: // different start point if printing shall not start from the top)
315: Rectangle allocation = new Rectangle(0, -startY, width, height
316: + startY);
317:
318: // set initial value for height where this print object should be broken
319: double breakHeight = maxHeight.getPoints() / scale; // in points, without scale
320:
321: /*
322: * calculate a new break height according to the contents, possibly
323: * smaller to break before some content (i.e. not to break in the
324: * middle of something
325: */
326: View rootView = mPane.getUI().getRootView(mPane);
327: breakHeight = calcBreakHeightFromView(rootView, allocation,
328: breakHeight);
329:
330: // create remainder
331: cHTMLPart remainder = new cHTMLPart(mScaleAllowed);
332: remainder.setHTML((HTMLDocument) mPane.getDocument());
333: remainder.setStartY(mStartY);
334:
335: // modify "this" to start where remainder ends
336: cUnit newStartY = new cCmUnit();
337:
338: if (breakHeight < height) {
339: newStartY.setPoints(mStartY.getPoints() + breakHeight);
340: } else { // this happends if there's nothing left for the next page
341: newStartY = mStartY.add(contentSize.getHeight());
342: }
343:
344: this .setStartY(newStartY);
345:
346: return remainder;
347: }
348:
349: /**
350: * Private utility to calculate break height based on the contents
351: * of a view. If the break height calculated is not smaller than the
352: * actual break height, actBreakHeight is returned unchanged.
353: * @param view The View object to operate on
354: * @param allocation Allocation for the view (where to render)
355: * @param actBreakHeight Actual break height
356: */
357: private double calcBreakHeightFromView(View view, Shape allocation,
358: double actBreakHeight) {
359: if (view.getViewCount() > 0) {
360: // child views exist - operate recursively on these
361: double breakHeight = actBreakHeight;
362: Shape childAllocation;
363: View childView;
364:
365: for (int i = 0; i < view.getViewCount(); i++) {
366: childAllocation = view
367: .getChildAllocation(i, allocation);
368:
369: if (childAllocation != null) {
370: childView = view.getView(i);
371:
372: // calculate break height for child, and use updated
373: // value in the further processing
374: breakHeight = calcBreakHeightFromView(childView,
375: childAllocation, breakHeight);
376: }
377: }
378:
379: return breakHeight; // return (possibly) updated value
380: } else {
381: // no childs - we have a leaf view (i.e. with contents)
382: double allocY = allocation.getBounds().getY();
383: double allocMaxY = allocation.getBounds().getMaxY();
384: if ((allocY >= 0) && (allocY < actBreakHeight)
385: && (allocMaxY > actBreakHeight)) {
386: // view starts on page and exceeds it
387:
388: /*
389: * If the height of a view exceeds the paperheight, there should
390: * be no break before (since it will be impossible to fit it in
391: * anywhere => an infinite loop). We don't have access to the
392: * pageheight here, therefore an "educated guess" is made:
393: * No breaks are inserted before views starting within the first
394: * 1% (chosen to avoid round-off errors) of the available space
395: * given by actBreakHeight. If the view starts after the first 1%,
396: * a break is inserted and the view will start at the top of the
397: * next page (i.e. withing the first 1% this time).
398: */
399: if (allocY < (actBreakHeight * 0.01)) {
400: return actBreakHeight; // unchanged, i.e. no breaks before this view
401: } else {
402: // view can be broken
403: if (allocY < actBreakHeight) {
404: return allocY; // break before start of view
405: } else {
406: return actBreakHeight; // unchanged
407: }
408: }
409: } else {
410: return actBreakHeight; // unchanged
411: }
412: }
413: }
414: }
415:
416: /**
417: * Utility class used for loading html synchroniously into a jTextPane
418: * @author Karl Peder Olesen (karlpeder), 20030604
419: */
420: class SyncHTMLEditorKit extends HTMLEditorKit {
421: /**
422: * Create an uninitialized text storage model that is appropriate for
423: * this type of editor.<br>
424: * The document returned will load synchroniously.
425: *
426: * @see javax.swing.text.EditorKit#createDefaultDocument()
427: */
428: public Document createDefaultDocument() {
429: Document doc = super .createDefaultDocument();
430: ((HTMLDocument) doc).setAsynchronousLoadPriority(-1);
431:
432: return doc;
433: }
434: }
|