PDFlib Cookbook

cookbook

textflow/widows_and_orphans

Create multi-column text output which may span multiple pages.

Download Java Code  Switch to PHP Code  Show Output 

/*
 * Textflow starter:
 * Create multi-column text output which may span multiple pages
 *
 * Required software: PDFlib/PDFlib+PDI/PPS 10
 * Required data: none
 */
package com.pdflib.cookbook.pdflib.textflow;

import com.pdflib.pdflib;
import com.pdflib.PDFlibException;

public class widows_and_orphans {
    public static void main(String argv[]) {
        /* This is where the data files are. Adjust as necessary. */
        String searchpath = "../input";

        pdflib p = null;

        String outfile = "widows_and_orphans.pdf";
        String title = "Widows and Orphans";
        int exitcode = 0;

        try {
            p = new pdflib();

            p.set_option("searchpath={" + searchpath + "}");

            /* This means we must check return values of load_font() etc. */
            p.set_option("errorpolicy=return");

            if (p.begin_document(outfile, "") == -1)
                throw new Exception("Error: " + p.get_errmsg());

            p.set_info("Creator", "PDFlib Cookbook");
            p.set_info("Title", title);
            
            double pagewidth = 500;
            double pageheight = 500;
            
            double header_fontsize = 24;

            /*
             * Text that together with the specified fontsize below provokes
             * an orphan.
             */
            String orphan_explanation = repeatString("This text provokes an \"orphan\" at the end of the "
                + "first fitbox. ", 10);
            String orphan = repeatString("Unless action is taken, the first line of this paragraph appears "
                + "as an orphan at the end of the first fitbox. ", 2);
            String orphan_more_text = repeatString("This is text after the paragraph with the orphan. ", 2);
            final String orphan_text = orphan_explanation
                + "<nextline leading=80%><nextparagraph leading=100%>"
                + orphan
                + "<nextline leading=80%><nextparagraph leading=100%>"
                + orphan_more_text;
              
            /*
             * Format the text that provokes an orphan without any special
             * action to avoid widows and orphans.
             */
            format_text_simple(p, orphan_text, 13.1,
                pagewidth, pageheight,
                header_fontsize,
                "Orphan");
            
            /*
             * Format the same text with an algorithm that avoids widows and
             * orphans.
             */
            format_text_widow_orphan_aware(p, orphan_text, 13.1,
                pagewidth, pageheight,
                header_fontsize,
                "Orphan avoided");
            
            /*
             * Text that together with the specified fontsize below provokes
             * a widow.
             */
            String widow_explanation = repeatString("This text provokes a \"widow\" at the start of the "
                + "second fitbox. ", 7);
            String widow = repeatString("Unless action is taken, the last line of this paragraph appears "
                + "as a widow at the start of the second fitbox. ", 2);
            String widow_more_text = repeatString("This is text after the paragraph with the widow. ", 2);
            final String widow_text = widow_explanation
                + "<nextline leading=80%><nextparagraph leading=100%>"
                + widow
                + "<nextline leading=80%><nextparagraph leading=100%>"
                + widow_more_text;
            
            /*
             * Format the text that provokes a widow without any special
             * action to avoid widows and orphans.
             */
            format_text_simple(p, widow_text, 13.6,
                pagewidth, pageheight,
                header_fontsize,
                "Widow");
            
            /*
             * Format the same text with an algorithm that avoids widows and
             * orphans.
             */
            format_text_widow_orphan_aware(p, widow_text, 13.6,
                pagewidth, pageheight,
                header_fontsize,
                "Widow avoided");
            
            p.end_document("");

        }
        catch (PDFlibException e) {
            System.err.println("PDFlib exception occurred:");
            System.err.println("[" + e.get_errnum() + "] " + e.get_apiname() +
                ": " + e.get_errmsg());
            exitcode = 1;
        }
        catch (Exception e) {
            System.err.println(e);
            exitcode = 1;
        }
        finally {
            if (p != null) {
                p.delete();
            }
            System.exit(exitcode);
        }
    }

    /**
     * Format the textflow into the fitboxes over multiple pages, without doing
     * anything to avoid widows and orphans.
     * 
     * @param p
     *            the PDFlib object
     * @param text
     *            text with inline options for textflow
     * @param fontsize
     *            size of font for text
     * @param pagewidth
     *            width of the page
     * @param pageheight
     *            height of the page
     * @param header_fontsize
     *            fontsize to use for the page header
     * @param title
     *            title for pages
     *            
     * @throws PDFlibException
     */
    private static void format_text_simple(pdflib p,
        String text, double fontsize,
        double pagewidth, double pageheight,
        double header_fontsize, String title)
            throws PDFlibException, Exception {
        
        final String optlist = "fontname=NotoSerif-Regular "
            + "fontsize=" + fontsize + " alignment=left adjustmethod=nofit";
        
        int tf = p.create_textflow(text, optlist);
        if (tf == -1) {
            throw new Exception("Error: Unable to create textflow: " +
                p.get_errmsg());
        }
        
        double fitbox_width = pagewidth * 0.5;
        double fitbox_height = pageheight * 0.5;
        
        double llx = (pagewidth - fitbox_width) / 2;
        double lly = (pageheight - fitbox_height) / 2;
        double urx = llx + fitbox_width;
        double ury = lly + fitbox_height;
        
        String header_options = "fontname=NotoSerif-Regular " 
            + "fontsize=" + header_fontsize + " position={center} boxsize={"
            + pagewidth + " " + header_fontsize + "}";
        
        /*
         * Simple algorithm to format the Textflow into as many fitboxes
         * as necessary.
         */
        int pagecount;
        String result;
        for (result = "_boxfull", pagecount = 1;
                result.equals("_boxfull"); pagecount += 1) {
            
            p.begin_page_ext(pagewidth, pageheight, "");

            p.fit_textline(title + " (page " + pagecount + ")", 0,
                pageheight - 2 * header_fontsize, header_options);

            result = p.fit_textflow(tf, llx, lly, urx, ury, "showborder=true");

            p.end_page_ext("");
        }
        
        p.delete_textflow(tf);
    }
    
    /**
     * Format the textflow into the fitboxes over multiple pages, while
     * applying an algorithm that avoids widows and orphans.
     * 
     * @param p
     *            the PDFlib object
     * @param text
     *            text with inline options for textflow
     * @param fontsize
     *            size of font for text
     * @param pagewidth
     *            width of the page
     * @param pageheight
     *            height of the page
     * @param header_fontsize
     *            fontsize to use for the page header
     * @param title
     *            title of first page
     *            
     * @throws PDFlibException
     */
    private static void format_text_widow_orphan_aware(pdflib p,
        String text, double fontsize,
        double pagewidth, double pageheight,
        double header_fontsize, String title)
            throws PDFlibException, Exception {
        
        /*
         * Same option list as in format_text_simple(), but with
         * minlinecount=2 to avoid orphans. 
         */
        final String optlist = "minlinecount=2 fontname=NotoSerif-Regular "
            + "fontsize=" + fontsize + " alignment=left adjustmethod=nofit";

        int tf = p.create_textflow(text, optlist);
        if (tf == -1) {
            throw new Exception("Error: Unable to create textflow: " +
                        p.get_errmsg());
        }
      
        double fitbox_width = pagewidth * 0.5;
        double fitbox_height = pageheight * 0.5;
        
        double llx = (pagewidth - fitbox_width) / 2;
        double lly = (pageheight - fitbox_height) / 2;
        double urx = llx + fitbox_width;
        double ury = lly + fitbox_height;
        
        String header_options = "fontname=NotoSerif-Regular " 
            + "fontsize=" + header_fontsize + " position={center} boxsize={"
            + pagewidth + " " + header_fontsize + "}";
        
        int pagecount;
        String result;
        for (result = "_boxfull", pagecount = 1;
                result.equals("_boxfull"); pagecount += 1) {
            
            p.begin_page_ext(pagewidth, pageheight, "");

            /*
             * Fit the the remaining Textflow into the first fitbox in blind
             * mode (option blind=true), i.e. without creating any real output,
             * while setting "minlinecount=2" to avoid an orphan. Query the
             * number of lines in the fitbox by using keyword "boxlinecount".
             */
            result = p.fit_textflow(tf, llx, lly, urx, ury, "blind=true");

            int boxlinecount = (int) p.info_textflow(tf, "boxlinecount");

            /*
             * Count how many times the textflow must be rewound: At least
             * one time for the first blind fit, and another time if a second
             * blind fit is performed because the fitbox is full.
             */
            int rewindcount = 1;
            
            if (result.equals("_boxfull")) {
                
                /*
                 * Fit the next part of the Textflow into the second fitbox in
                 * blind mode. We don't care here that the second fitbox will
                 * actually be placed on the next page in the output file.
                 */
                p.fit_textflow(tf, llx, lly, urx, ury, "blind=true");

                /*
                 * Query the number of lines of the first paragraph in the
                 * second fitbox. If the count is equal to one, we found a
                 * single-line "widow". In order to avoid that, we reduce the
                 * number of lines for the first fitbox by one.
                 */
                int firstparalinecount = (int) p.info_textflow(tf,
                    "firstparalinecount");
                if (firstparalinecount == 1) {
                    boxlinecount -= 1;
                }
                
                rewindcount += 1;
            }

            /* Place header line */
            p.fit_textline(title + " (page " + pagecount + ")", 0,
                pageheight - 2 * header_fontsize, header_options);

            /*
             * Now do the actual output on the page.
             * Rewind the Textflow one or two steps (determined by
             * rewindcount) and set the calculated maximum number of lines.
             */
            result = p.fit_textflow(tf, llx, lly, urx, ury,
                "rewind=-" + rewindcount
                + " maxlines=" + boxlinecount + " showborder=true");

            p.end_page_ext("");
        }
        
        p.delete_textflow(tf);
    }
    
    /**
     * Repeat a string n times.
     * 
     * @param s
     *            the string to repeat
     * @param n
     *            how many times to repeat
     * @return n times the string s
     */
    private static String repeatString(String s, int n) {
        return new String(new char[n]).replace("\0", s);
    }
}