PDFlib Cookbook

cookbook

fonts/opentype_feature_tester

Demonstrate all supported typographic OpenType features after checking whether a particular feature is supported in a font.

Download Java Code  Switch to PHP Code  Show Output 

/*
 * Test supported OpenType features in a font
 *
 * Demonstrate supported typographic OpenType features after checking
 * whether a particular feature is supported in a font. Only those features
 * will be shown which are
 * a) available in the font
 * b) are supported by PDFlib's "features" option
 * c) are not used automatically for shaping and layout features
 * 
 * Notes:
 * - The sample text used for showing a particular font feature may
 *   or may not match the font's feature. If a feature is listed, but no
 *   effect is visible you may have to switch to different test characters.
 *   In the test strings below we use character sequences which are typical
 *   for the tested features, but naturally a font may support different
 *   sequences.
 * - OpenType features for Chinese, Japanese, and Korean text are
 *   demonstrated in the opentype_features_for_cjk Cookbook topic.
 *
 * Required software: PDFlib/PDFlib+PDI/PPS 10
 * Required data: NotoSerif-Regular and NotoSerif-Bold fonts
 *                (replace with suitable font with OpenType feature tables)
 */

package com.pdflib.cookbook.pdflib.fonts;

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

class opentype_feature_tester {
    public static void main(String argv[]) {
        /* This is where the data files are. Adjust as necessary. */
        final String searchpath = "../input";
        final String outfile = "opentype_feature_tester.pdf";
        final String title = "OpenType Feature Tester";

        String optlist;
        pdflib p = null;
        int i, group, table, font;
        final double llx = 50, lly = 50, urx = 800, ury = 550;
        String result;
        int exitcode = 0;

        /* 
         * Name of the test font. Replace with the name of your OpenType font.
         */
        final String testfont = "NotoSerif-Regular";

        final String headers[] = {
            "Feature", "Description", "Feature disabled", "Feature enabled"
        };

        final String groupdescriptions[] = {
            "OpenType features for western typography",
            "Stylistic sets",
            "Character variants"
        };

        class testcase {
            testcase(String feature,
                            String additionaloptions,
                            String description,
                            String text) {
                this.feature = feature;
                this.additionaloptions = additionaloptions;
                this.description = description;
                this.text = text;
            }

            String feature;
            String additionaloptions; // for script and language options
            String description;
            String text;
        }

        /*
         * Features supported by PDFlib along with common test strings. Change
         * the test strings to match the features in the tested font.
         */
        final testcase testcases[][] = {
            {
                /* Western features */
                new testcase("aalt", "", "access all alternates",
                        "{(123ABCDhij)}"),
                new testcase("afrc", "", "alternative fractions",
                                "1/2 1/4 3/4"),
                new testcase("c2pc", "", "petite capitals from capitals",
                                "ABCDEFG"),
                new testcase("c2sc", "", "small capitals from capitals",
                                "ABCDEFG"),
                new testcase("calt", "", "contextual alternates",
                                "ABCDEFG"),
                new testcase("ccmp", "", "glyph composition",
                                "Ą Ư ᾰ̓́"),
                new testcase("clig", "", "contextual ligatures",
                                "ABCDEFG"),
                new testcase("cswh", "", "contextual swash",
                                "ABCDEFG"),
                new testcase("dlig", "", "discretionary ligatures",
                    "ch tz st c/o TEL"),
                new testcase("dnom", "", "denominators",
                                "0123456789"),
                new testcase("falt", "", "final glyph alternates",
                                "abcdefghijABCDEFGH0123"),
                new testcase("fina", "", "final forms",
                                "σ"),
                new testcase("frac", "", "fractions",
                                "1/2 1/4 3/4"),
                new testcase("hist", "", "historical forms",
                                "s"),
                new testcase("hlig", "", "historical ligatures",
                    "ct st &.longs;b &.longs;t"),
                new testcase("init", "", "initial forms",
                                "abcdefghijABCDEFGH0123"),
                new testcase("isol", "", "isolated forms",
                                "abcdefghijABCDEFGH0123"),
                new testcase("liga", "", "standard ligatures",
                                "ff fi fl ffi ffl"),
                new testcase("lnum", "", "lining figures",
                                "0123456789"),
                new testcase("locl", "script=latn language=ROM",
                                "localized forms",
                                "&.Scedilla;&.Tcedilla;"),
                new testcase("medi", "", "medial forms",
                                "abcdefghijABCDEFGH0123"),
                new testcase("mgrk", "", "mathematical Greek",
                                "Ωμ"),
                new testcase("numr", "", "numerators",
                                "0123456789"),
                new testcase("onum", "", "old-style figures",
                                "0123456789"),
                new testcase("ordn", "", "ordinals",
                                "a o 1o 2a 3o"),
                new testcase("ornm", "", "ornaments",
                                "• abcqrstuvwABC"),
                new testcase("pcap", "", "petite capitals",
                                "ABCDEFG"),
                new testcase("pnum", "", "proportional figures",
                                "0123456789"),
                new testcase("salt", "", "stylistic alternates",
                                "& abcdefgh"),
                new testcase("sinf", "", "scientific inferiors",
                                "H2SO4"),
                new testcase("smcp", "", "small capitals",
                                "PostScript"),
                new testcase("subs", "", "subscript ",
                                "0123456789"),
                new testcase("sups", "", "superscript",
                                "0123456789"),
                new testcase("swsh", "", "swash",
                                "PQRSTpqrst &"),
                new testcase("titl", "", "titling",
                                "ABCDEFG"),
                new testcase("tnum", "", "tabular figures",
                                "0123456789"),
                new testcase("unic", "", "unicase",
                                "Filosofia"),
                new testcase("zero", "", "slashed zero", "0"),
            },
            {
                /* Stylistic Sets
                 *
                 * Modify the text as appropriate for your font.
                 */
                new testcase("ss01", "", "stylistic set 1",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss02", "", "stylistic set 2",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss03", "", "stylistic set 3",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss04", "", "stylistic set 4",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss05", "", "stylistic set 5",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss06", "", "stylistic set 6",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss07", "", "stylistic set 7",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss08", "", "stylistic set 8",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss09", "", "stylistic set 9",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss10", "", "stylistic set 10",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss11", "", "stylistic set 11",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss12", "", "stylistic set 12",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss13", "", "stylistic set 13",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss14", "", "stylistic set 14",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss15", "", "stylistic set 15",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss16", "", "stylistic set 16",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss17", "", "stylistic set 17",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss18", "", "stylistic set 18",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss19", "", "stylistic set 19",
                    "abcdefghijABCDEFGH0123"),
                new testcase("ss20", "", "stylistic set 20",
                    "abcdefghijABCDEFGH0123"),
            },
            {
                /* Character variants
                 * We include only a few here; the full set comprises
                 * cv01-cv99.
                 *
                 * The examples below are suitable for the Andika and
                 * GentiumPlus fonts which are available at
                 * https://software.sil.org/andika/
                 * https://software.sil.org/gentium/
                 *
                 * Modify the text and feature numbers as appropriate for your font.
                 */
                new testcase("cv13", "", "character variant 13",
                    "Ɓ"),
                new testcase("cv28", "", "character variant 28",
                    "Ħ"),
                new testcase("cv43", "", "character variant 43",
                    "Ŋ"),
                new testcase("cv47", "", "character variant 47",
                    "Ȣȣ&#1D3D;&#1D15;"),
                new testcase("cv91", "", "character variant 91",
                    "˥˦˧˨˩꜒꜓꜔꜕꜖"),
            }
        };

        try {
            p = new pdflib();
            int row, col;

            p.set_option("searchpath={" + searchpath + "}");
            p.set_option("charref=true");
            /*
             * This means that formatting and other errors will raise an
             * exception. This simplifies our sample code, but is not
             * recommended for production.
             */
            p.set_option("errorpolicy=exception");

            /* Set an output path according to the name of the topic */
            if (p.begin_document(outfile, "") == -1) {
                throw new Exception("Error: " + p.get_apiname() + ": "
                    + p.get_errmsg());
            }

            p.set_info("Creator", "PDFlib Cookbook");
            p.set_info("Title", title);

            table = -1;
            row = 1;
            col = 1;

            /* Table header */
            optlist = "fittextline={fontname=NotoSerif-Bold fontsize=12} margin=4";
            table = p.add_table_cell(table, col, row++,
                "OpenType features in font " + testfont, optlist
                    + " colspan=4");

            for (i = 0; i < headers.length; i++) {
                col = i + 1;
                table = p.add_table_cell(table, col, row, headers[i], optlist);
            }
            row++;

            for (group = 0; group < testcases.length; group += 1) {
                int featurecount = 0;

                col = 1;

                optlist = "fittextline={fontname=NotoSerif-Bold fontsize=12} "
                    + "margin=4 colspan=4 rowjoingroup= " + group;
                table = p.add_table_cell(table, col, row++,
                    groupdescriptions[group], optlist);

                /*
                 * Create a table with feature samples, one feature per table
                 * row
                 */
                for (i = 0; i < testcases[group].length; i += 1) {
                    final testcase testcase = testcases[group][i];
                    col = 1;

                    /* skip unavailable features */
                    font = p.load_font(testfont, "unicode", "");
                    optlist = "name=" + testcase.feature;
                    if (p.info_font(font, "feature", optlist) != 1)
                        continue;

                    featurecount++;

                    /* Common option list for columns 1-3 */
                    optlist = "fittextline={fontname=NotoSerif-Regular "
                        +  "fontsize=12} margin=4";

                    /* Column 1: feature name */
                    table = p.add_table_cell(table, col++, row,
                        testcase.feature + " " + testcase.additionaloptions,
                        optlist);

                    /* Column 2: feature description */
                    table = p.add_table_cell(table, col++, row,
                        testcase.description, optlist);

                    /* Column 3: raw input text with all features disabled
                     * (especially those which are enabled by default)
                     */
                    optlist = "fittextline={fontname={" + testfont
                        + "} fontsize=12 "
                        + "features={_none} } margin=4";
                    table = p.add_table_cell(table, col++, row, testcase.text,
                        optlist);

                    /* Column 4: text with the tested feature enabled.
                     * We first disable all other features to avoid undesired
                     * interactions between features. This can happen in rare
                     * cases with features which are enabled by default. 
                     */
                    optlist = "fittextline={fontname={" + testfont
                        + "} fontsize=12 "
                        + "features={_none " + testcase.feature +  "} "
                        + testcase.additionaloptions + "} margin=4";
                    table = p.add_table_cell(table, col++, row, testcase.text,
                        optlist);

                    row++;
                }

                if (featurecount == 0) {
                    col = 1;

                    optlist = "fittextline={fontname=NotoSerif-Regular "
                        +  "fontsize=12} "
                        + "margin=4 colspan=4 rowjoingroup= " + group;
                    table = p.add_table_cell(table, col, row++, "(none available in the font)",
                        optlist);
                }
            }

            /*
             * Loop until all of the table is placed; create new pages as long
             * as more table instances need to be placed.
             */
            do {
                p.begin_page_ext(0, 0, "width=a4.height height=a4.width");

                optlist = "header=2 fill={{area=rowodd fillcolor={gray 0.9}}} "
                    + "stroke={{line=other}} ";

                /* Place the table instance */
                result = p.fit_table(table, llx, lly, urx, ury, optlist);

                if (result.equals("_error"))
                    throw new Exception("Couldn't place table: "
                        + p.get_errmsg());

                p.end_page_ext("");

            }
            while (result.equals("_boxfull"));

            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);
        }
    }
}