PDFlib Cookbook

cookbook

text_output/emoji_sequences

Modify emoji with Unicode sequences.

Download Java Code  Switch to PHP Code  Show Output 

/*
 * 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.
 *
 * A "String" object in Java represents strings in UTF-16 format.
 * This means that Unicode values outside the BMP, i.e. > U+FFFF,
 * must be represented as surrogate pairs. Unicode code points outside the
 * BMP cannot be represented in a single "char" or "Character".
 *
 * You can initialize Strings in the following ways:
 * - Supply the Unicode code point as "int" value and convert it to a String:
 *   String unistring = new String(Character.toChars(0x1F0CF));
 *
 * - Supply the surrogate pair (two UTF-16 code units):
 *   String unistring = new String("\uD83C\uDCCF");
 *   
 * - Supply the Unicode character directly in the Java source and
 *   store it in a suitable Unicode encoding such as UTF-8 or UTF-16
 *   (not the commonly used cp1252):
 *   String unistring = new String("X");
 *   (Using "X" as placeholder here to keep this source in cp1252).
 *
 *   The Java compiler must be informed about the source encoding,
 *   either directly with the javac option encoding="utf8" or
 *   by appropriate settings in the IDE.
 *   
 * - PDFlib character references are independent of the development
 *   environment and can be enabled with the "charref" option, e.g.
 *   p.fit_textline("🃏",
 *         100, 100, "charref=true fontname=EmojiOneColor fontsize=50");
 */
package com.pdflib.cookbook.pdflib.text_output;

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

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

        pdflib p = null;
        int i, table;
        final double llx = 25, lly = 25, urx = 820, ury = 550;
        String result;
        int exitcode = 0;

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

        final String headers[] = {
            "Description",
            "Font name",
            "Raw input",
            "Isolated characters",
            "Combined characters"
        };

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

        final testcase testcases[] = {
            new testcase("ZWJ grouping", "",
            	"👨‍👩‍👧"),
            
            new testcase("skin tone", "",
            	"👦🏻👦🏼👦🏽" +
            	"👦🏾👦🏿"),
            
            new testcase("emoji flag", "",
            	"🇲🇹 🇦🇪 🇬🇧"),
            
            new testcase("emoji keycap", "",
            	"0️⃣ 9️⃣ #️⃣"),

            /* 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",
                "🏊‍♀️" +
                "🏊‍♂️"),
            
            new testcase("ZWJ gender", "Segoe UI Emoji",
                    "🧜‍♂ 🧜‍♀"),
            
            new testcase("skin tone and gender", "Segoe UI Emoji",
                    "🧜🏿‍♂ 🧜🏿‍♀"),
            
            new testcase("ZWJ hair components: red/bald/white", "Segoe UI Emoji",
                "👨‍🦰" +
            	"👨‍🦲" +
            	"👨‍🦳"),
            
            new testcase("ZWJ role: man farmer", "Segoe UI Emoji",
                "👨‍🌾"),
            
            new testcase("ZWJ role: woman farmer", "Segoe UI Emoji",
                    "👩‍🌾"),
            */
        };

        try {
            String optlist;

            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, "") == -1) {
                throw new Exception("Error: " + p.get_errmsg());
            }

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

            table = -1;

            /* Table header */
            for (i = 0; i < headers.length; i++) {
                final int 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 < testcases.length; i += 1) {
                final testcase testcase = testcases[i];
                final int row = i + 2;
                int tf;

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

                int 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(-1, testcase.text,
                		"charref=false fontname=NotoSerif-Regular " +
                        "fontsize=" +(testcase.text.length() < 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.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);
        }
    }
}