History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/swing/text/TextComponentPrintable.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/swing/text/TextComponentPrintable.java+839−0
@@ -0,0 +1,839 @@
1+/*
2+ * Copyright (c) 2005, 2006, Oracle and/or its affiliates. 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. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+package sun.swing.text;
26+
27+import java.awt.ComponentOrientation;
28+import java.awt.Dimension;
29+import java.awt.Font;
30+import java.awt.FontMetrics;
31+import java.awt.Graphics;
32+import java.awt.Graphics2D;
33+import java.awt.Insets;
34+import java.awt.Rectangle;
35+import java.awt.Component;
36+import java.awt.Container;
37+import java.awt.font.FontRenderContext;
38+import java.awt.print.PageFormat;
39+import java.awt.print.Printable;
40+import java.awt.print.PrinterException;
41+import java.text.MessageFormat;
42+import java.util.ArrayList;
43+import java.util.Collections;
44+import java.util.List;
45+import java.util.concurrent.Callable;
46+import java.util.concurrent.ExecutionException;
47+import java.util.concurrent.FutureTask;
48+import java.util.concurrent.atomic.AtomicReference;
49+
50+import javax.swing.BorderFactory;
51+import javax.swing.CellRendererPane;
52+import javax.swing.JTextField;
53+import javax.swing.JTextArea;
54+import javax.swing.JEditorPane;
55+import javax.swing.JViewport;
56+import javax.swing.JScrollPane;
57+import javax.swing.JTextPane;
58+import javax.swing.SwingUtilities;
59+import javax.swing.border.Border;
60+import javax.swing.border.TitledBorder;
61+import javax.swing.text.BadLocationException;
62+import javax.swing.text.JTextComponent;
63+import javax.swing.text.Document;
64+import javax.swing.text.EditorKit;
65+import javax.swing.text.AbstractDocument;
66+import javax.swing.text.html.HTMLDocument;
67+import javax.swing.text.html.HTML;
68+
69+import sun.font.FontDesignMetrics;
70+
71+import 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+ */
98+public 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+}