QPDFFormFieldObjectHelper::generateTextAppearance(
    QPDFAnnotationObjectHelper& aoh)
{
    QPDFObjectHandle AS = aoh.getAppearanceStream("/N");
    if (AS.isNull())
    {
        QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AS from scratch");
        QPDFObjectHandle::Rectangle rect = aoh.getRect();
        QPDFObjectHandle::Rectangle bbox(
            0, 0, rect.urx - rect.llx, rect.ury - rect.lly);
        QPDFObjectHandle dict = QPDFObjectHandle::parse(
            "<< /Resources << /ProcSet [ /PDF /Text ] >>"
            " /Type /XObject /Subtype /Form >>");
        dict.replaceKey("/BBox", QPDFObjectHandle::newFromRectangle(bbox));
        AS = QPDFObjectHandle::newStream(
            this->oh.getOwningQPDF(), "/Tx BMC\nEMC\n");
        AS.replaceDict(dict);
        QPDFObjectHandle AP = aoh.getAppearanceDictionary();
        if (AP.isNull())
        {
            QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AP from scratch");
            aoh.getObjectHandle().replaceKey(
                "/AP", QPDFObjectHandle::newDictionary());
            AP = aoh.getAppearanceDictionary();
        }
        AP.replaceKey("/N", AS);
    }
    if (! AS.isStream())
    {
        aoh.getObjectHandle().warnIfPossible(
            "unable to get normal appearance stream for update");
        return;
    }
    QPDFObjectHandle bbox_obj = AS.getDict().getKey("/BBox");
    if (! bbox_obj.isRectangle())
    {
        aoh.getObjectHandle().warnIfPossible(
            "unable to get appearance stream bounding box");
        return;
    }
    QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
    std::string DA = getDefaultAppearance();
    std::string V = getValueAsString();
    std::vector<std::string> opt;
    if (isChoice() && ((getFlags() & ff_ch_combo) == 0))
    {
        opt = getChoices();
    }

    TfFinder tff;
    Pl_QPDFTokenizer tok("tf", &tff);
    tok.write(QUtil::unsigned_char_pointer(DA.c_str()), DA.length());
    tok.finish();
    double tf = tff.getTf();
    DA = tff.getDA();

    std::string (*encoder)(std::string const&, char) = &QUtil::utf8_to_ascii;
    std::string font_name = tff.getFontName();
    if (! font_name.empty())
    {
        // See if the font is encoded with something we know about.
        QPDFObjectHandle resources = AS.getDict().getKey("/Resources");
        QPDFObjectHandle font = getFontFromResource(resources, font_name);
        if (! font.isInitialized())
        {
            QPDFObjectHandle dr = getInheritableFieldValue("/DR");
            font = getFontFromResource(dr, font_name);
        }
        if (font.isDictionary() &&
            font.getKey("/Encoding").isName())
        {
            std::string encoding = font.getKey("/Encoding").getName();
            if (encoding == "/WinAnsiEncoding")
            {
                QTC::TC("qpdf", "QPDFFormFieldObjectHelper WinAnsi");
                encoder = &QUtil::utf8_to_win_ansi;
            }
            else if (encoding == "/MacRomanEncoding")
            {
                encoder = &QUtil::utf8_to_mac_roman;
            }
        }
    }

    V = (*encoder)(V, '?');
    for (size_t i = 0; i < opt.size(); ++i)
    {
        opt.at(i) = (*encoder)(opt.at(i), '?');
    }

    AS.addTokenFilter(new ValueSetter(DA, V, opt, tf, bbox));
}
QPDFFormFieldObjectHelper::generateTextAppearance(
    QPDFAnnotationObjectHelper& aoh)
{
    QPDFObjectHandle AS = aoh.getAppearanceStream("/N");
    if (AS.isNull())
    {
        QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AS from scratch");
        QPDFObjectHandle::Rectangle rect = aoh.getRect();
        QPDFObjectHandle::Rectangle bbox(
            0, 0, rect.urx - rect.llx, rect.ury - rect.lly);
        QPDFObjectHandle dict = QPDFObjectHandle::parse(
            "<< /Resources << /ProcSet [ /PDF /Text ] >>"
            " /Type /XObject /Subtype /Form >>");
        dict.replaceKey("/BBox", QPDFObjectHandle::newFromRectangle(bbox));
        AS = QPDFObjectHandle::newStream(
            this->oh.getOwningQPDF(), "/Tx BMC\nEMC\n");
        AS.replaceDict(dict);
        QPDFObjectHandle AP = aoh.getAppearanceDictionary();
        if (AP.isNull())
        {
            QTC::TC("qpdf", "QPDFFormFieldObjectHelper create AP from scratch");
            aoh.getObjectHandle().replaceKey(
                "/AP", QPDFObjectHandle::newDictionary());
            AP = aoh.getAppearanceDictionary();
        }
        AP.replaceKey("/N", AS);
    }
    if (! AS.isStream())
    {
        aoh.getObjectHandle().warnIfPossible(
            "unable to get normal appearance stream for update");
        return;
    }
    QPDFObjectHandle bbox_obj = AS.getDict().getKey("/BBox");
    if (! bbox_obj.isRectangle())
    {
        aoh.getObjectHandle().warnIfPossible(
            "unable to get appearance stream bounding box");
        return;
    }
    QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
    std::string DA = getDefaultAppearance();
    std::string V = getValueAsString();
    std::vector<std::string> opt;
    if (isChoice() && ((getFlags() & ff_ch_combo) == 0))
    {
        opt = getChoices();
    }

    TfFinder tff;
    Pl_QPDFTokenizer tok("tf", &tff);
    tok.write(QUtil::unsigned_char_pointer(DA.c_str()), DA.length());
    tok.finish();
    double tf = tff.getTf();
    DA = tff.getDA();

    std::string (*encoder)(std::string const&, char) = &QUtil::utf8_to_ascii;
    std::string font_name = tff.getFontName();
    if (! font_name.empty())
    {
         
        QPDFObjectHandle resources = AS.getDict().getKey("/Resources");
        QPDFObjectHandle font = getFontFromResource(resources, font_name);
        if (! font.isInitialized())
        {
            QPDFObjectHandle dr = getInheritableFieldValue("/DR");
            font = getFontFromResource(dr, font_name);
        }
        if (font.isDictionary() &&
            font.getKey("/Encoding").isName())
        {
            std::string encoding = font.getKey("/Encoding").getName();
            if (encoding == "/WinAnsiEncoding")
            {
                QTC::TC("qpdf", "QPDFFormFieldObjectHelper WinAnsi");
                encoder = &QUtil::utf8_to_win_ansi;
            }
            else if (encoding == "/MacRomanEncoding")
            {
                encoder = &QUtil::utf8_to_mac_roman;
            }
        }
    }

    V = (*encoder)(V, '?');
    for (size_t i = 0; i < opt.size(); ++i)
    {
        opt.at(i) = (*encoder)(opt.at(i), '?');
    }

    AS.addTokenFilter(new ValueSetter(DA, V, opt, tf, bbox));
}