PDFlib Cookbook

cookbook

fonts/opentype_features_for_cjk

Demonstrate the use of OpenType features for CJK fonts and text.

Download PHP Code  Switch to Java Code  Show Output 

<?php
/*
 * Demonstrate OpenType font features for Chinese, Japanese, and Korean text
 *
 * Demonstrate various CJK OpenType features after checking
 * whether a particular feature is supported in a font.
 *
 * Required software: PDFlib/PDFlib+PDI/PPS 10
 * Required data: CJK font with OpenType CJK features
 * 
 * Note that even if a particular OpenType feature is present in the font
 * it may not necessarily affect the characters provided in the test strings
 * below. In this case you must replace the test strings with content which
 * is suited to your font.
 */

/* This is where the data files are. Adjust as necessary. */
$searchpath = dirname(__FILE__,3)."/input";

$outfile = "";

$i; $table; $font;
$llx = 25; $lly = 20; $urx = 825; $ury = 580;
$result;

$testfont = "NotoSerifCJKjp-Regular";

$headers = array( "Description", "Option list",
    "Font name", "Raw input (feature disabled)", "Feature enabled" );

class testcase {
    function __construct($description, $optlist,
                $feature, $text) {
        $this->description = $description;
        $this->optlist = $optlist;
        $this->feature = $feature;
        $this->text = $text;
    }
}

$testcases = array(
    new testcase("Localized forms for simplified Chinese",
                "features={locl} script=hang language=ZHS", "locl",
                "\u{2E95}\u{2EDD}\u{38C0}"),

    new testcase("Localized forms for traditional Chinese",
                "features={locl} script=hang language=ZHT", "locl",
                "\u{328B}\u{35EB}\u{3B35}"),

    new testcase("Localized forms for Korean",
                "features={locl} script=hang language=KOR", "locl",
                "\u{4E73}\u{52E4}\u{52FB}"),

    new testcase("Simplified forms",
                "features={smpl}", "smpl",
                "\u{6AAF} \u{81FA} \u{98B1}"),

    new testcase("Traditional forms",
                "features={trad}", "trad",
                "\u{53F0}\u{4E03}\u{4E0E}\u{4E31}\u{7D1B}\u{9912}\u{96B8}" .
                "\u{9698}\u{966A}\u{939A}"),

    new testcase("Expert forms",
                "features={expt}", "expt",
                "\u{5516}\u{82A6}\u{98F4}\u{9E78}\u{9D60}\u{6F23}\u{84EE}\u{7BDD}"),

    new testcase("Full widths",
                "features={fwid}", "fwid",
                "0123-456(789)=25"),

    new testcase("Horizontal kana alternates",
                "features={hkna}", "hkna",
                "\u{3041}\u{3042}\u{3043}\u{3044} \u{30A1}\u{30A2}\u{30A3}" .
                "\u{30A4}\u{30E5}\u{30E4}\u{30B4}\u{30A7}"),

    new testcase("Hangul",
                "features={hngl}", "hngl",
                "\u{4F3D}"),

    new testcase("Hojo kanji forms (JIS X 0212-1990)",
                "features={hojo}", "hojo",
                "\u{6FF9}\u{9B35}\u{8200}\u{9CE6}\u{9B2D}\u{9721}\u{884B}"),

    new testcase("Half widths",
                "features={hwid}", "hwid",
                "0123-456(789)=25"),

    new testcase("Italics",
                "features={ital}", "ital",
                "ABCD abcd 1234"),

    new testcase("JIS2004 forms",
                "features={jp04}", "jp04",
                "\u{9022}\u{98F4}\u{6EA2}\u{8328}\u{9C2F}\u{6DEB}\u{8FC2}"),

    new testcase("JIS78 forms",
                "features={jp78}", "jp78",
                "\u{5516}\u{53F1}\u{63B4}\u{8000}"),

    new testcase("JIS83 forms",
                "features={jp83}", "jp83",
                "\u{4E08}\u{51A4}"),

    new testcase("JIS90 forms",
                "features={jp90}", "jp90",
                "\u{5026}\u{6994}"),

    new testcase("Alternate annotation forms",
                "features={nalt}", "nalt",
                "ABC123\u{002F}\u{30B4}\u{217B}\u{4E0B}\u{7981}\u{2F29}\u{4E2D}\u{2F42}"),

    new testcase("NLC kanji forms",
                "features={nlck}", "nlck",
                "\u{4FA0}\u{64B9}"),

    new testcase("Proportional kana",
                "features={pkna}", "pkna",
                "\u{3041}\u{3042} \u{30A1}\u{30A2} \u{FF66} \u{FF67}"),

    new testcase("Proportional widths",
                "features={pwid}", "pwid",
                "\u{3041}\u{3042} \u{30A1}\u{30A2} \u{301C}\u{FF21}\u{FF23}\u{FF28}" .
                "\u{FF29}\u{FF2D} ABCD abcd 1234"),

    new testcase("Quarter widths",
                "features={qwid}", "qwid",
                "0123-456(789)=25"),

    new testcase("Ruby notation forms",
                "features={ruby}", "ruby",
                "\u{3042}\u{304A}\u{307D}\u{30AA}\u{30BC}\u{30F1}\u{25C9}\u{31F0}\u{31FF}"),

    new testcase("Traditional name forms",
                "features={tnam}", "tnam",
                "\u{4E9C}"),

    new testcase("Third widths",
                "features={twid}", "twid",
                "0123-456(789)=25"),

    new testcase("Vertical kana alternates",
                "vertical features={vkna}", "vkna",
                "\u{3041}\u{3042}\u{3043}\u{3044}\u{31FF}\u{31F8}\u{30CB}\u{30BB}"),

    // "vrt2" and "vert" are activated automatically in vertical mode
    new testcase("Vertical alternates and rotation (vrt2)",
                "vertical", "vrt2",
                "\u{FF08}ab\u{FF09}\u{FF1A}\u{300C}ab\u{300D}\u{3348}"),
    
    new testcase("Vertical alternates (vert)",
                "vertical", "vert",
                "\u{FF08}ab\u{FF09}\u{FF1A}\u{300C}ab\u{300D}\u{3348}"),                      
);

try {
    $p = new pdflib();

    $p->set_option("searchpath={" . $searchpath . "}");
    $p->set_option("charref=true");
    $p->set_option("escapesequence=true");

    /*
     * This means that formatting and other errors will raise an
     * exception. This simplifies our sample code, but is not
     * recommended for production code.
     */
    $p->set_option("errorpolicy=exception");

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

    $p->set_info("Creator", "PDFlib Cookbook");
    $p->set_info("Title", "opentype_features_for_cjk");

    $table = 0;

    /* Table header */
    for ($i = 0; $i < count($headers); $i++) {
        $col = $i + 1;

        $optlist =
            "fittextline={fontname=NotoSerif-Bold fontsize=12} "
            . "margin=4";
        $table = $p->add_table_cell($table, $col, 1, $headers[$i], $optlist);
    }

    /* Create a table with feature samples, one feature per table row */
    for ($i = 0; $i < count($testcases); $i += 1) {
        $testcase = $testcases[$i];
        $row = $i + 2;

        $col = 1;

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

        /* Column 1: feature description */
        $table = $p->add_table_cell($table, $col++, $row,
                $testcase->description, $optlist);

        /* Column 2: option list */
        $table = $p->add_table_cell($table, $col++, $row, $testcase->optlist,
                        $optlist);

        /* Column 3: font name */
        $table = $p->add_table_cell($table, $col++, $row, $testfont, $optlist);

        /* Column 4: raw input text with feature disabled */
        $optlist = "fittextline={fontname={" . $testfont
                . "} fontsize=12 } margin=4";
        $table = $p->add_table_cell($table, $col++, $row, $testcase->text,
                $optlist);

        /*
         * Column 5: text with enabled feature, or warning if the
         * feature is not available in the font
         */
        $font = $p->load_font($testfont, "unicode", "");

        /* Check whether font contains the required feature table */
        $optlist = "name=" . $testcase->feature;
        if ($p->info_font($font, "feature", $optlist) == 1) {
            /* feature is available: apply it to the text */
            $optlist = "margin=4 fittextline={fontname={" . $testfont
                    . "} fontsize=12 " . $testcase->optlist . "}";
            $table = $p->add_table_cell($table, $col++, $row, $testcase->text,
                    $optlist);
        }
        else {
            /* feature is not available: emit a warning */
            $optlist = "fittextline={fontname=NotoSerif-Regular "
                    . "fontsize=12 fillcolor=red} margin=4";
            $table = $p->add_table_cell($table, $col++, $row,
                    "(feature not available in this 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=1 fill={{area=rowodd fillcolor={gray 0.9}}} "
            . "stroke={{line=other}} debugshow";

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

        if ($result == "_error")
            throw new Exception("Couldn't place table: "
                . $p->get_errmsg());

        $p->end_page_ext("");

    }
    while ($result == "_boxfull");
    
    $p->end_document("");
    $buf = $p->get_buffer();
    $len = strlen($buf);

    header("Content-type: application/pdf");
    header("Content-Length: $len");
    header("Content-Disposition: inline; filename=opentype_features_for_cjk.pdf");
    print $buf;

} catch (PDFlibException $e) {
    echo ("PDFlib exception occurred:\n".
        "[" . $e->get_errnum() . "] " . $e->get_apiname() .
        ": " . $e->get_errmsg() . "\n");
    exit(1);
} catch (Throwable $e) {
    echo ("PHP exception occurred: " . $e->getMessage() . "\n");
    exit(1);
}

$p = 0;

?>