blob: 20824ef2a58c0db00b121bf86a26a661bfd293c5 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package sun.swing.text;
26
27import java.awt.ComponentOrientation;
28import java.awt.Dimension;
29import java.awt.Font;
30import java.awt.FontMetrics;
31import java.awt.Graphics;
32import java.awt.Graphics2D;
33import java.awt.Insets;
34import java.awt.Rectangle;
35import java.awt.Component;
36import java.awt.Container;
37import java.awt.font.FontRenderContext;
38import java.awt.print.PageFormat;
39import java.awt.print.Printable;
40import java.awt.print.PrinterException;
41import java.text.MessageFormat;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.List;
45import java.util.concurrent.Callable;
46import java.util.concurrent.ExecutionException;
47import java.util.concurrent.FutureTask;
48import java.util.concurrent.atomic.AtomicReference;
49
50import javax.swing.BorderFactory;
51import javax.swing.CellRendererPane;
52import javax.swing.JTextField;
53import javax.swing.JTextArea;
54import javax.swing.JEditorPane;
55import javax.swing.JViewport;
56import javax.swing.JScrollPane;
57import javax.swing.JTextPane;
58import javax.swing.SwingUtilities;
59import javax.swing.border.Border;
60import javax.swing.border.TitledBorder;
61import javax.swing.text.BadLocationException;
62import javax.swing.text.JTextComponent;
63import javax.swing.text.Document;
64import javax.swing.text.EditorKit;
65import javax.swing.text.AbstractDocument;
66import javax.swing.text.html.HTMLDocument;
67import javax.swing.text.html.HTML;
68
69import sun.font.FontDesignMetrics;
70
71import sun.swing.text.html.FrameEditorPaneTag;
72
73/**
74 * An implementation of {@code Printable} to print {@code JTextComponent} with
75 * the header and footer.
76 *
77 * <h1>
78 * WARNING: this class is to be used in
79 * javax.swing.text.JTextComponent only.
80 * </h1>
81 *
82 * <p>
83 * The implementation creates a new {@code JTextComponent} ({@code printShell})
84 * to print the content using the {@code Document}, {@code EditorKit} and
85 * rendering-affecting properties from the original {@code JTextComponent}.
86 *
87 * <p>
88 * {@code printShell} is laid out on the first {@code print} invocation.
89 *
90 * <p>
91 * This class can be used on any thread. Part of the implementation is executed
92 * on the EDT though.
93 *
94 * @author Igor Kushnirskiy
95 *
96 * @since 1.6
97 */
98public class TextComponentPrintable implements CountingPrintable {
99
100
101 private static final int LIST_SIZE = 1000;
102
103 private boolean isLayouted = false;
104
105 /*
106 * The text component to print.
107 */
108 private final JTextComponent textComponentToPrint;
109
110 /*
111 * The FontRenderContext to layout and print with
112 */
113 private final AtomicReference<FontRenderContext> frc =
114 new AtomicReference<FontRenderContext>(null);
115
116 /**
117 * Special text component used to print to the printer.
118 */
119 private final JTextComponent printShell;
120
121 private final MessageFormat headerFormat;
122 private final MessageFormat footerFormat;
123
124 private static final float HEADER_FONT_SIZE = 18.0f;
125 private static final float FOOTER_FONT_SIZE = 12.0f;
126
127 private final Font headerFont;
128 private final Font footerFont;
129
130 /**
131 * stores metrics for the unhandled rows. The only metrics we need are
132 * yStart and yEnd when row is handled by updatePagesMetrics it is removed
133 * from the list. Thus the head of the list is the fist row to handle.
134 *
135 * sorted
136 */
137 private final List<IntegerSegment> rowsMetrics;
138
139 /**
140 * thread-safe list for storing pages metrics. The only metrics we need are
141 * yStart and yEnd.
142 * It has to be thread-safe since metrics are calculated on
143 * the printing thread and accessed on the EDT thread.
144 *
145 * sorted
146 */
147 private final List<IntegerSegment> pagesMetrics;
148
149 /**
150 * Returns {@code TextComponentPrintable} to print {@code textComponent}.
151 *
152 * @param textComponent {@code JTextComponent} to print
153 * @param headerFormat the page header, or {@code null} for none
154 * @param footerFormat the page footer, or {@code null} for none
155 * @return {@code TextComponentPrintable} to print {@code textComponent}
156 */
157 public static Printable getPrintable(final JTextComponent textComponent,
158 final MessageFormat headerFormat,
159 final MessageFormat footerFormat) {
160
161 if (textComponent instanceof JEditorPane
162 && isFrameSetDocument(textComponent.getDocument())) {
163 //for document with frames we create one printable per
164 //frame and merge them with the CompoundPrintable.
165 List<JEditorPane> frames = getFrames((JEditorPane) textComponent);
166 List<CountingPrintable> printables =
167 new ArrayList<CountingPrintable>();
168 for (JEditorPane frame : frames) {
169 printables.add((CountingPrintable)
170 getPrintable(frame, headerFormat, footerFormat));
171 }
172 return new CompoundPrintable(printables);
173 } else {
174 return new TextComponentPrintable(textComponent,
175 headerFormat, footerFormat);
176 }
177 }
178
179 /**
180 * Checks whether the document has frames. Only HTMLDocument might
181 * have frames.
182 *
183 * @param document the {@code Document} to check
184 * @return {@code true} if the {@code document} has frames
185 */
186 private static boolean isFrameSetDocument(final Document document) {
187 boolean ret = false;
188 if (document instanceof HTMLDocument) {
189 HTMLDocument htmlDocument = (HTMLDocument)document;
190 if (htmlDocument.getIterator(HTML.Tag.FRAME).isValid()) {
191 ret = true;
192 }
193 }
194 return ret;
195 }
196
197
198 /**
199 * Returns frames under the {@code editor}.
200 * The frames are created if necessary.
201 *
202 * @param editor the {@JEditorPane} to find the frames for
203 * @return list of all frames
204 */
205 private static List<JEditorPane> getFrames(final JEditorPane editor) {
206 List<JEditorPane> list = new ArrayList<JEditorPane>();
207 getFrames(editor, list);
208 if (list.size() == 0) {
209 //the frames have not been created yet.
210 //let's trigger the frames creation.
211 createFrames(editor);
212 getFrames(editor, list);
213 }
214 return list;
215 }
216
217 /**
218 * Adds all {@code JEditorPanes} under {@code container} tagged by {@code
219 * FrameEditorPaneTag} to the {@code list}. It adds only top
220 * level {@code JEditorPanes}. For instance if there is a frame
221 * inside the frame it will return the top frame only.
222 *
223 * @param c the container to find all frames under
224 * @param list {@code List} to append the results too
225 */
226 private static void getFrames(final Container container, List<JEditorPane> list) {
227 for (Component c : container.getComponents()) {
228 if (c instanceof FrameEditorPaneTag
229 && c instanceof JEditorPane ) { //it should be always JEditorPane
230 list.add((JEditorPane) c);
231 } else {
232 if (c instanceof Container) {
233 getFrames((Container) c, list);
234 }
235 }
236 }
237 }
238
239 /**
240 * Triggers the frames creation for {@code JEditorPane}
241 *
242 * @param editor the {@code JEditorPane} to create frames for
243 */
244 private static void createFrames(final JEditorPane editor) {
245 Runnable doCreateFrames =
246 new Runnable() {
247 public void run() {
248 final int WIDTH = 500;
249 final int HEIGHT = 500;
250 CellRendererPane rendererPane = new CellRendererPane();
251 rendererPane.add(editor);
252 //the values do not matter
253 //we only need to get frames created
254 rendererPane.setSize(WIDTH, HEIGHT);
255 };
256 };
257 if (SwingUtilities.isEventDispatchThread()) {
258 doCreateFrames.run();
259 } else {
260 try {
261 SwingUtilities.invokeAndWait(doCreateFrames);
262 } catch (Exception e) {
263 if (e instanceof RuntimeException) {
264 throw (RuntimeException) e;
265 } else {
266 throw new RuntimeException(e);
267 }
268 }
269 }
270 }
271
272 /**
273 * Constructs {@code TextComponentPrintable} to print {@code JTextComponent}
274 * {@code textComponent} with {@code headerFormat} and {@code footerFormat}.
275 *
276 * @param textComponent {@code JTextComponent} to print
277 * @param headerFormat the page header or {@code null} for none
278 * @param footerFormat the page footer or {@code null} for none
279 */
280 private TextComponentPrintable(JTextComponent textComponent,
281 MessageFormat headerFormat,
282 MessageFormat footerFormat) {
283 this.textComponentToPrint = textComponent;
284 this.headerFormat = headerFormat;
285 this.footerFormat = footerFormat;
286 headerFont = textComponent.getFont().deriveFont(Font.BOLD,
287 HEADER_FONT_SIZE);
288 footerFont = textComponent.getFont().deriveFont(Font.PLAIN,
289 FOOTER_FONT_SIZE);
290 this.pagesMetrics =
291 Collections.synchronizedList(new ArrayList<IntegerSegment>());
292 this.rowsMetrics = new ArrayList<IntegerSegment>(LIST_SIZE);
293 this.printShell = createPrintShell(textComponent);
294 }
295
296
297 /**
298 * creates a printShell.
299 * It creates closest text component to {@code textComponent}
300 * which uses {@code frc} from the {@code TextComponentPrintable}
301 * for the {@code getFontMetrics} method.
302 *
303 * @param textComponent {@code JTextComponent} to create a
304 * printShell for
305 * @return the print shell
306 */
307 private JTextComponent createPrintShell(final JTextComponent textComponent) {
308 if (SwingUtilities.isEventDispatchThread()) {
309 return createPrintShellOnEDT(textComponent);
310 } else {
311 FutureTask<JTextComponent> futureCreateShell =
312 new FutureTask<JTextComponent>(
313 new Callable<JTextComponent>() {
314 public JTextComponent call() throws Exception {
315 return createPrintShellOnEDT(textComponent);
316 }
317 });
318 SwingUtilities.invokeLater(futureCreateShell);
319 try {
320 return futureCreateShell.get();
321 } catch (InterruptedException e) {
322 throw new RuntimeException(e);
323 } catch (ExecutionException e) {
324 Throwable cause = e.getCause();
325 if (cause instanceof Error) {
326 throw (Error) cause;
327 }
328 if (cause instanceof RuntimeException) {
329 throw (RuntimeException) cause;
330 }
331 throw new AssertionError(cause);
332 }
333 }
334 }
335 private JTextComponent createPrintShellOnEDT(final JTextComponent textComponent) {
336 assert SwingUtilities.isEventDispatchThread();
337
338 JTextComponent ret = null;
339 if (textComponent instanceof JTextField) {
340 ret =
341 new JTextField() {
342 {
343 setHorizontalAlignment(
344 ((JTextField) textComponent).getHorizontalAlignment());
345 }
346 @Override
347 public FontMetrics getFontMetrics(Font font) {
348 return (frc.get() == null)
349 ? super.getFontMetrics(font)
350 : FontDesignMetrics.getMetrics(font, frc.get());
351 }
352 };
353 } else if (textComponent instanceof JTextArea) {
354 ret =
355 new JTextArea() {
356 {
357 JTextArea textArea = (JTextArea) textComponent;
358 setLineWrap(textArea.getLineWrap());
359 setWrapStyleWord(textArea.getWrapStyleWord());
360 setTabSize(textArea.getTabSize());
361 }
362 @Override
363 public FontMetrics getFontMetrics(Font font) {
364 return (frc.get() == null)
365 ? super.getFontMetrics(font)
366 : FontDesignMetrics.getMetrics(font, frc.get());
367 }
368 };
369 } else if (textComponent instanceof JTextPane) {
370 ret =
371 new JTextPane() {
372 @Override
373 public FontMetrics getFontMetrics(Font font) {
374 return (frc.get() == null)
375 ? super.getFontMetrics(font)
376 : FontDesignMetrics.getMetrics(font, frc.get());
377 }
378 @Override
379 public EditorKit getEditorKit() {
380 if (getDocument() == textComponent.getDocument()) {
381 return ((JTextPane) textComponent).getEditorKit();
382 } else {
383 return super.getEditorKit();
384 }
385 }
386 };
387 } else if (textComponent instanceof JEditorPane) {
388 ret =
389 new JEditorPane() {
390 @Override
391 public FontMetrics getFontMetrics(Font font) {
392 return (frc.get() == null)
393 ? super.getFontMetrics(font)
394 : FontDesignMetrics.getMetrics(font, frc.get());
395 }
396 @Override
397 public EditorKit getEditorKit() {
398 if (getDocument() == textComponent.getDocument()) {
399 return ((JEditorPane) textComponent).getEditorKit();
400 } else {
401 return super.getEditorKit();
402 }
403 }
404 };
405 }
406 //want to occupy the whole width and height by text
407 ret.setBorder(null);
408
409 //set properties from the component to print
410 ret.setOpaque(textComponent.isOpaque());
411 ret.setEditable(textComponent.isEditable());
412 ret.setEnabled(textComponent.isEnabled());
413 ret.setFont(textComponent.getFont());
414 ret.setBackground(textComponent.getBackground());
415 ret.setForeground(textComponent.getForeground());
416 ret.setComponentOrientation(
417 textComponent.getComponentOrientation());
418
419 if (ret instanceof JEditorPane) {
420 ret.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES,
421 textComponent.getClientProperty(
422 JEditorPane.HONOR_DISPLAY_PROPERTIES));
423 ret.putClientProperty(JEditorPane.W3C_LENGTH_UNITS,
424 textComponent.getClientProperty(JEditorPane.W3C_LENGTH_UNITS));
425 ret.putClientProperty("charset",
426 textComponent.getClientProperty("charset"));
427 }
428 ret.setDocument(textComponent.getDocument());
429 return ret;
430 }
431
432
433
434
435 /**
436 * Returns the number of pages in this printable.
437 * <p>
438 * This number is defined only after {@code print} returns NO_SUCH_PAGE.
439 *
440 * @return the number of pages.
441 */
442 public int getNumberOfPages() {
443 return pagesMetrics.size();
444 }
445
446 /**
447 * See Printable.print for the API description.
448 *
449 * There are two parts in the implementation.
450 * First part (print) is to be called on the printing thread.
451 * Second part (printOnEDT) is to be called on the EDT only.
452 *
453 * print triggers printOnEDT
454 */
455 public int print(final Graphics graphics,
456 final PageFormat pf,
457 final int pageIndex) throws PrinterException {
458 if (!isLayouted) {
459 if (graphics instanceof Graphics2D) {
460 frc.set(((Graphics2D)graphics).getFontRenderContext());
461 }
462 layout((int)Math.floor(pf.getImageableWidth()));
463 calculateRowsMetrics();
464 }
465 int ret;
466 if (!SwingUtilities.isEventDispatchThread()) {
467 Callable<Integer> doPrintOnEDT = new Callable<Integer>() {
468 public Integer call() throws Exception {
469 return printOnEDT(graphics, pf, pageIndex);
470 }
471 };
472 FutureTask<Integer> futurePrintOnEDT =
473 new FutureTask<Integer>(doPrintOnEDT);
474 SwingUtilities.invokeLater(futurePrintOnEDT);
475 try {
476 ret = futurePrintOnEDT.get();
477 } catch (InterruptedException e) {
478 throw new RuntimeException(e);
479 } catch (ExecutionException e) {
480 Throwable cause = e.getCause();
481 if (cause instanceof PrinterException) {
482 throw (PrinterException)cause;
483 } else if (cause instanceof RuntimeException) {
484 throw (RuntimeException) cause;
485 } else if (cause instanceof Error) {
486 throw (Error) cause;
487 } else {
488 throw new RuntimeException(cause);
489 }
490 }
491 } else {
492 ret = printOnEDT(graphics, pf, pageIndex);
493 }
494 return ret;
495 }
496
497
498 /**
499 * The EDT part of the print method.
500 *
501 * This method is to be called on the EDT only. Layout should be done before
502 * calling this method.
503 */
504 private int printOnEDT(final Graphics graphics,
505 final PageFormat pf,
506 final int pageIndex) throws PrinterException {
507 assert SwingUtilities.isEventDispatchThread();
508 Border border = BorderFactory.createEmptyBorder();
509 //handle header and footer
510 if (headerFormat != null || footerFormat != null) {
511 //Printable page enumeration is 0 base. We need 1 based.
512 Object[] formatArg = new Object[]{Integer.valueOf(pageIndex + 1)};
513 if (headerFormat != null) {
514 border = new TitledBorder(border,
515 headerFormat.format(formatArg),
516 TitledBorder.CENTER, TitledBorder.ABOVE_TOP,
517 headerFont, printShell.getForeground());
518 }
519 if (footerFormat != null) {
520 border = new TitledBorder(border,
521 footerFormat.format(formatArg),
522 TitledBorder.CENTER, TitledBorder.BELOW_BOTTOM,
523 footerFont, printShell.getForeground());
524 }
525 }
526 Insets borderInsets = border.getBorderInsets(printShell);
527 updatePagesMetrics(pageIndex,
528 (int)Math.floor(pf.getImageableHeight()) - borderInsets.top
529 - borderInsets.bottom);
530
531 if (pagesMetrics.size() <= pageIndex) {
532 return NO_SUCH_PAGE;
533 }
534
535 Graphics2D g2d = (Graphics2D)graphics.create();
536
537 g2d.translate(pf.getImageableX(), pf.getImageableY());
538 border.paintBorder(printShell, g2d, 0, 0,
539 (int)Math.floor(pf.getImageableWidth()),
540 (int)Math.floor(pf.getImageableHeight()));
541
542 g2d.translate(0, borderInsets.top);
543 //want to clip only vertically
544 Rectangle clip = new Rectangle(0, 0,
545 (int) pf.getWidth(),
546 pagesMetrics.get(pageIndex).end
547 - pagesMetrics.get(pageIndex).start + 1);
548
549 g2d.clip(clip);
550 int xStart = 0;
551 if (ComponentOrientation.RIGHT_TO_LEFT ==
552 printShell.getComponentOrientation()) {
553 xStart = (int) pf.getImageableWidth() - printShell.getWidth();
554 }
555 g2d.translate(xStart, - pagesMetrics.get(pageIndex).start);
556 printShell.print(g2d);
557
558 g2d.dispose();
559
560 return Printable.PAGE_EXISTS;
561 }
562
563
564 private boolean needReadLock = false;
565
566 /**
567 * Tries to release document's readlock
568 *
569 * Note: Not to be called on the EDT.
570 */
571 private void releaseReadLock() {
572 assert ! SwingUtilities.isEventDispatchThread();
573 Document document = textComponentToPrint.getDocument();
574 if (document instanceof AbstractDocument) {
575 try {
576 ((AbstractDocument) document).readUnlock();
577 needReadLock = true;
578 } catch (Error ignore) {
579 // readUnlock() might throw StateInvariantError
580 }
581 }
582 }
583
584
585 /**
586 * Tries to acquire document's readLock if it was released
587 * in releaseReadLock() method.
588 *
589 * Note: Not to be called on the EDT.
590 */
591 private void acquireReadLock() {
592 assert ! SwingUtilities.isEventDispatchThread();
593 if (needReadLock) {
594 try {
595 /*
596 * wait until all the EDT events are processed
597 * some of the document changes are asynchronous
598 * we need to make sure we get the lock after those changes
599 */
600 SwingUtilities.invokeAndWait(
601 new Runnable() {
602 public void run() {
603 }
604 });
605 } catch (InterruptedException ignore) {
606 } catch (java.lang.reflect.InvocationTargetException ignore) {
607 }
608 Document document = textComponentToPrint.getDocument();
609 ((AbstractDocument) document).readLock();
610 needReadLock = false;
611 }
612 }
613
614 /**
615 * Prepares {@code printShell} for printing.
616 *
617 * Sets properties from the component to print.
618 * Sets width and FontRenderContext.
619 *
620 * Triggers Views creation for the printShell.
621 *
622 * There are two parts in the implementation.
623 * First part (layout) is to be called on the printing thread.
624 * Second part (layoutOnEDT) is to be called on the EDT only.
625 *
626 * {@code layout} triggers {@code layoutOnEDT}.
627 *
628 * @param width width to layout the text for
629 */
630 private void layout(final int width) {
631 if (!SwingUtilities.isEventDispatchThread()) {
632 Callable<Object> doLayoutOnEDT = new Callable<Object>() {
633 public Object call() throws Exception {
634 layoutOnEDT(width);
635 return null;
636 }
637 };
638 FutureTask<Object> futureLayoutOnEDT = new FutureTask<Object>(
639 doLayoutOnEDT);
640
641 /*
642 * We need to release document's readlock while printShell is
643 * initializing
644 */
645 releaseReadLock();
646 SwingUtilities.invokeLater(futureLayoutOnEDT);
647 try {
648 futureLayoutOnEDT.get();
649 } catch (InterruptedException e) {
650 throw new RuntimeException(e);
651 } catch (ExecutionException e) {
652 Throwable cause = e.getCause();
653 if (cause instanceof RuntimeException) {
654 throw (RuntimeException) cause;
655 } else if (cause instanceof Error) {
656 throw (Error) cause;
657 } else {
658 throw new RuntimeException(cause);
659 }
660 } finally {
661 acquireReadLock();
662 }
663 } else {
664 layoutOnEDT(width);
665 }
666
667 isLayouted = true;
668 }
669
670 /**
671 * The EDT part of layout method.
672 *
673 * This method is to be called on the EDT only.
674 */
675 private void layoutOnEDT(final int width) {
676 assert SwingUtilities.isEventDispatchThread();
677 //need to have big value but smaller than MAX_VALUE otherwise
678 //printing goes south due to overflow somewhere
679 final int HUGE_INTEGER = Integer.MAX_VALUE - 1000;
680
681 CellRendererPane rendererPane = new CellRendererPane();
682
683 //need to use JViewport since text is layouted to the viewPort width
684 //otherwise it will be layouted to the maximum text width
685 JViewport viewport = new JViewport();
686 viewport.setBorder(null);
687 Dimension size = new Dimension(width, HUGE_INTEGER);
688
689 //JTextField is a special case
690 //it layouts text in the middle by Y
691 if (printShell instanceof JTextField) {
692 size =
693 new Dimension(size.width, printShell.getPreferredSize().height);
694 }
695 printShell.setSize(size);
696 viewport.setComponentOrientation(printShell.getComponentOrientation());
697 viewport.setSize(size);
698 viewport.add(printShell);
699 rendererPane.add(viewport);
700 }
701
702 /**
703 * Calculates pageMetrics for the pages up to the {@code pageIndex} using
704 * {@code rowsMetrics}.
705 * Metrics are stored in the {@code pagesMetrics}.
706 *
707 * @param pageIndex the page to update the metrics for
708 * @param pageHeight the page height
709 */
710 private void updatePagesMetrics(final int pageIndex, final int pageHeight) {
711 while (pageIndex >= pagesMetrics.size() && !rowsMetrics.isEmpty()) {
712 // add one page to the pageMetrics
713 int lastPage = pagesMetrics.size() - 1;
714 int pageStart = (lastPage >= 0)
715 ? pagesMetrics.get(lastPage).end + 1
716 : 0;
717 int rowIndex;
718 for (rowIndex = 0;
719 rowIndex < rowsMetrics.size()
720 && (rowsMetrics.get(rowIndex).end - pageStart + 1)
721 <= pageHeight;
722 rowIndex++) {
723 }
724 if (rowIndex == 0) {
725 // can not fit a single row
726 // need to split
727 pagesMetrics.add(
728 new IntegerSegment(pageStart, pageStart + pageHeight - 1));
729 } else {
730 rowIndex--;
731 pagesMetrics.add(new IntegerSegment(pageStart,
732 rowsMetrics.get(rowIndex).end));
733 for (int i = 0; i <= rowIndex; i++) {
734 rowsMetrics.remove(0);
735 }
736 }
737 }
738 }
739
740 /**
741 * Calculates rowsMetrics for the document. The result is stored
742 * in the {@code rowsMetrics}.
743 *
744 * Two steps process.
745 * First step is to find yStart and yEnd for the every document position.
746 * Second step is to merge all intersected segments ( [yStart, yEnd] ).
747 */
748 private void calculateRowsMetrics() {
749 final int documentLength = printShell.getDocument().getLength();
750 List<IntegerSegment> documentMetrics = new ArrayList<IntegerSegment>(LIST_SIZE);
751 Rectangle rect;
752 for (int i = 0, previousY = -1, previousHeight = -1; i < documentLength;
753 i++) {
754 try {
755 rect = printShell.modelToView(i);
756 if (rect != null) {
757 int y = (int) rect.getY();
758 int height = (int) rect.getHeight();
759 if (height != 0
760 && (y != previousY || height != previousHeight)) {
761 /*
762 * we do not store the same value as previous. in our
763 * documents it is often for consequent positons to have
764 * the same modelToView y and height.
765 */
766 previousY = y;
767 previousHeight = height;
768 documentMetrics.add(new IntegerSegment(y, y + height - 1));
769 }
770 }
771 } catch (BadLocationException e) {
772 assert false;
773 }
774 }
775 /*
776 * Merge all intersected segments.
777 */
778 Collections.sort(documentMetrics);
779 int yStart = Integer.MIN_VALUE;
780 int yEnd = Integer.MIN_VALUE;
781 for (IntegerSegment segment : documentMetrics) {
782 if (yEnd < segment.start) {
783 if (yEnd != Integer.MIN_VALUE) {
784 rowsMetrics.add(new IntegerSegment(yStart, yEnd));
785 }
786 yStart = segment.start;
787 yEnd = segment.end;
788 } else {
789 yEnd = segment.end;
790 }
791 }
792 if (yEnd != Integer.MIN_VALUE) {
793 rowsMetrics.add(new IntegerSegment(yStart, yEnd));
794 }
795 }
796
797 /**
798 * Class to represent segment of integers.
799 * we do not call it Segment to avoid confusion with
800 * javax.swing.text.Segment
801 */
802 private static class IntegerSegment implements Comparable<IntegerSegment> {
803 final int start;
804 final int end;
805
806 IntegerSegment(int start, int end) {
807 this.start = start;
808 this.end = end;
809 }
810
811 public int compareTo(IntegerSegment object) {
812 int startsDelta = start - object.start;
813 return (startsDelta != 0) ? startsDelta : end - object.end;
814 }
815
816 @Override
817 public boolean equals(Object obj) {
818 if (obj instanceof IntegerSegment) {
819 return compareTo((IntegerSegment) obj) == 0;
820 } else {
821 return false;
822 }
823 }
824
825 @Override
826 public int hashCode() {
827 // from the "Effective Java: Programming Language Guide"
828 int result = 17;
829 result = 37 * result + start;
830 result = 37 * result + end;
831 return result;
832 }
833
834 @Override
835 public String toString() {
836 return "IntegerSegment [" + start + ", " + end + "]";
837 }
838 }
839}