PDFlib Cookbook

cookbook

text_output/emoji_sequences

Modify emoji with Unicode sequences.

Download PHP Code  Switch to Java Code  Show Output 

<?php
/*
 * Emoji sequences
 * 
 * This topic demonstrates the following aspects:
 * - how to deal with supplemental Unicode characters in your code
 *
 * - font-specific features for color fonts:       
 *   - Emoji zerowidth joiner sequences for multi-person groupings, role,
 *     gender, hair components and other modifications using
 *     U+200D ZERO WIDTH JOINER (ZWJ)
 *     
 *   - Skintone modifiers for adjusting the skin color of emoji using
 *     U+1F3FB EMOJI MODIFIER FITZPATRICK TYPE 1-2
 *     ...
 *     U+1F3FF EMOJI MODIFIER FITZPATRICK TYPE 6
 *
 *   - Emoji flag sequences for creating flags from regional indicator
 *     characters U+1F1E6 REGIONAL INDICATOR SYMBOL LETTER A etc.
 *        
 *   - Emoji keycap sequences using digits and
 *     U+FE0F EMOJI PRESENTATION SELECTOR (VS16)
 *     U+20E3 COMBINING ENCLOSING KEYCAP
 *     
 *     These sequences are defined in the Unicode standard, but their
 *     implementation must be provided by the OpenType feature definitions
 *     in the font. Comprehensive tables are available at
 *     https://unicode.org/Public/emoji/14.0/emoji-zwj-sequences.txt
 *     https://unicode.org/Public/emoji/14.0/emoji-sequences.txt
 *
 * Required software: PDFlib/PDFlib+PDI/PPS 10
 * Required data: color emoji font
 * 
 * We use the font EmojiOneColor which offers a variety of colorful glyphs
 * as well as OpenType feature definitions for the modifications above.
 * It is freely available at
 * https://github.com/adobe-fonts/emojione-color
 * 
 * Some Unicode sequences are not supported by this font. You can use the
 * Windows font "Segoe UI Emoji" for additional examples (see below).
 */

/*
 * Many Emoji characters are encoded with values > U+FFFF (supplemental
 * characters) which requires special treatment depending on the
 * programming language and development environment.
 *
 * You can initialize PHP strings with supplemental characters in the
 * following ways:
 *
 * - Supply the Unicode code point in hexadecimal format (surrogate pairs are
 *   not supported):
 *   $unistring = "\u{1F0CF}";
 *
 * - Supply the Unicode character directly in the PHP source and store the
 *   source file in UTF-8 format.
 *
 * - PDFlib character references are independent of the development environment
 *   and can be enabled with the "charref" option, e.g.
 *   p.fit_textline("&#x1F0CF;", 100, 100,
 *       "charref=true fontname=EmojiOneColor fontsize=50");
 */

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

$exitcode = 0;

$llx = 25; $lly = 25; $urx = 820; $ury = 550;

/* This font will be used unless another one is specified in the table */
$defaulttestfont = "EmojiOneColor";

$headers = array(
    "Description",
    "Font name",
    "Raw input",
    "Isolated characters",
    "Combined characters"
);

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

$testcases = array(
    new testcase("ZWJ grouping", "",
        "&#x1F468;&zwj;&#x1F469;&zwj;&#x1F467;"),
    
    new testcase("skin tone", "",
        "&#x1F466;&#x1F3FB;&#x1F466;&#x1F3FC;&#x1F466;&#x1F3FD;" .
        "&#x1F466;&#x1F3FE;&#x1F466;&#x1F3FF;"),
    
    new testcase("emoji flag", "",
        "&#x1F1F2;&#x1F1F9; &#x1F1E6;&#x1F1EA; &#x1F1EC;&#x1F1E7;"),
    
    new testcase("emoji keycap", "",
        "0&#xFE0F;&#x20E3; 9&#xFE0F;&#x20E3; #&#xFE0F;&#x20E3;"),

    /* The following examples don't work with EmojiOneColor, but e.g.
        * with the Windows font "Segoe UI Emoji".
    new testcase("ZWJ gender", "Segoe UI Emoji",
        "&#x1F3CA;&zwj;&#x2640;&#xFE0F;" .
        "&#x1F3CA;&zwj;&#x2642;&#xFE0F;"),
    
    new testcase("ZWJ gender", "Segoe UI Emoji",
            "&#x1F9DC;&zwj;&#x2642; &#x1F9DC;&zwj;&#x2640;"),
    
    new testcase("skin tone and ZWJ gender", "Segoe UI Emoji",
            "&#x1F9DC;&#x1F3FF;&zwj;&#x2642; &#x1F9DC;&#x1F3FF;&zwj;&#x2640;"),
    
    new testcase("ZWJ hair components: red/bald/white", "Segoe UI Emoji",
        "&#x1F468;&zwj;&#x1F9B0;" .
        "&#x1F468;&zwj;&#x1F9B2;" .
        "&#x1F468;&zwj;&#x1F9B3;"),
    
    new testcase("ZWJ role: man farmer", "Segoe UI Emoji",
        "&#x1F468;&zwj;&#x1F33E;"),
    
    new testcase("ZWJ role: woman farmer", "Segoe UI Emoji",
            "&#x1F469;&zwj;&#x1F33E;"),
        */
);

try {

    $p = new pdflib();
    $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 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", $title);

    $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;

        /*
         * Use the entry in the test table if available, and the default
         * test font otherwise.
         */
        $testfont = 
            strlen($testcase->fontname) > 0 
                ? $testcase->fontname
                : $defaulttestfont;

        $col = 1;

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

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

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

        /* Column 3: raw input */
        $tf = $p->add_textflow(0, $testcase->text,
                "charref=false fontname=NotoSerif-Regular " .
                "fontsize=" .(strlen($testcase->text) < 35 ? 12 : 8) . " alignment=center");
        $table = $p->add_table_cell($table, $col++, $row, "",
                "colwidth=20% textflow=" . $tf . " margin=4");

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

        /* Column 5: text with enabled feature */
        $optlist = "fittextline={fontname={" . $testfont . "} fontsize=20} margin=4";
        $table = $p->add_table_cell($table, $col++, $row, $testcase->text,
                $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");
        
        /* Place the table */
        $optlist = "header=1 fill={{area=rowodd "
                . "fillcolor={gray 0.9}}} stroke={{line=other}} ";
        $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=emoji_sequences.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;
?>