예제 #1
0
파일: filter.cpp 프로젝트: aquileia/wesnoth
bool basic_unit_filter_impl::internal_matches_filter(const unit & u, const map_location& loc, const unit* u2) const
{
	if (!vcfg["name"].blank() && vcfg["name"].t_str() != u.name()) {
		return false;
	}

	if (!vcfg["id"].empty()) {
		std::vector<std::string> id_list = utils::split(vcfg["id"]);
		if (std::find(id_list.begin(), id_list.end(), u.id()) == id_list.end()) {
			return false;
		}
	}

	// Allow 'speaker' as an alternative to id, since people use it so often
	if (!vcfg["speaker"].blank() && vcfg["speaker"].str() != u.id()) {
		return false;
	}

	if (vcfg.has_child("filter_location")) {
		if (vcfg.count_children("filter_location") > 1) {
			FAIL("Encountered multiple [filter_location] children of a standard unit filter. "
				 "This is not currently supported and in all versions of wesnoth would have "
				 "resulted in the later children being ignored. You must use [and] or similar "
				 "to achieve the desired result.");
		}
		terrain_filter filt(vcfg.child("filter_location"), &fc_, use_flat_tod_);
		if (!filt.match(loc)) {
			return false;
		}
	}

	if(vcfg.has_child("filter_side")) {
		if (vcfg.count_children("filter_side") > 1) {
			FAIL("Encountered multiple [filter_side] children of a standard unit filter. "
				 "This is not currently supported and in all versions of wesnoth would have "
				 "resulted in the later children being ignored. You must use [and] or similar "
				 "to achieve the desired result.");
		}
		side_filter filt(vcfg.child("filter_side"), &fc_);
		if(!filt.match(u.side()))
			return false;
	}

	// Also allow filtering on location ranges outside of the location filter
	if (!vcfg["x"].blank() || !vcfg["y"].blank()){
		if(vcfg["x"] == "recall" && vcfg["y"] == "recall") {
			//locations on the map are considered to not be on a recall list
			if (fc_.get_disp_context().map().on_board(loc))
			{
				return false;
			}
		} else if(vcfg["x"].empty() && vcfg["y"].empty()) {
			return false;
		} else if(!loc.matches_range(vcfg["x"], vcfg["y"])) {
			return false;
		}
	}

	// The type could be a comma separated list of types
	if (!vcfg["type"].empty()) {
		std::vector<std::string> types = utils::split(vcfg["type"]);
		if (std::find(types.begin(), types.end(), u.type_id()) == types.end()) {
			return false;
		}
	}

	// Shorthand for all advancements of a given type
	if (!vcfg["type_tree"].empty()) {
		std::set<std::string> types;
		for(const std::string type : utils::split(vcfg["type_tree"])) {
			if(types.count(type)) {
				continue;
			}
			if(const unit_type* ut = unit_types.find(type)) {
				const auto& tree = ut->advancement_tree();
				types.insert(tree.begin(), tree.end());
				types.insert(type);
			}
		}
		if(types.find(u.type_id()) == types.end()) {
			return false;
		}
	}

	// The variation_type could be a comma separated list of types
	if (!vcfg["variation"].empty())
	{
		std::vector<std::string> types = utils::split(vcfg["variation"]);
		if (std::find(types.begin(), types.end(), u.variation()) == types.end()) {
			return false;
		}
	}

	// The has_variation_type could be a comma separated list of types
	if (!vcfg["has_variation"].empty())
	{
		bool match = false;
		// If this unit is a variation itself then search in the base unit's variations.
		const unit_type* const type = u.variation().empty() ? &u.type() : unit_types.find(u.type().base_id());
		assert(type);

		for (const std::string& variation_id : utils::split(vcfg["has_variation"])) {
			if (type->has_variation(variation_id)) {
				match = true;
				break;
			}
		}
		if (!match) return false;
	}

	if (!vcfg["ability"].empty())
	{
		bool match = false;

		for (const std::string& ability_id : utils::split(vcfg["ability"])) {
			if (u.has_ability_by_id(ability_id)) {
				match = true;
				break;
			}
		}
		if (!match) return false;
	}

	if (!vcfg["race"].empty()) {
		std::vector<std::string> races = utils::split(vcfg["race"]);
		if (std::find(races.begin(), races.end(), u.race()->id()) == races.end()) {
			return false;
		}
	}

	if (!vcfg["gender"].blank() && string_gender(vcfg["gender"]) != u.gender()) {
		return false;
	}

	if (!vcfg["side"].empty() && vcfg["side"].to_int(-999) != u.side()) {
		std::vector<std::string> sides = utils::split(vcfg["side"]);
		const std::string u_side = std::to_string(u.side());
		if (std::find(sides.begin(), sides.end(), u_side) == sides.end()) {
			return false;
		}
	}

	// handle statuses list
	if (!vcfg["status"].empty()) {
		bool status_found = false;

		for (const std::string status : utils::split(vcfg["status"])) {
			if(u.get_state(status)) {
				status_found = true;
				break;
			}
		}

		if(!status_found) {
			return false;
		}
	}

	if (vcfg.has_child("has_attack")) {
		const vconfig& weap_filter = vcfg.child("has_attack");
		bool has_weapon = false;
		for(const attack_type& a : u.attacks()) {
			if(a.matches_filter(weap_filter.get_parsed_config())) {
				has_weapon = true;
				break;
			}
		}
		if(!has_weapon) {
			return false;
		}
	} else if (!vcfg["has_weapon"].blank()) {
		std::string weapon = vcfg["has_weapon"];
		bool has_weapon = false;
		for(const attack_type& a : u.attacks()) {
			if(a.id() == weapon) {
				has_weapon = true;
				break;
			}
		}
		if(!has_weapon) {
			return false;
		}
	}

	if (!vcfg["role"].blank() && vcfg["role"].str() != u.get_role()) {
		return false;
	}

	if (!vcfg["ai_special"].blank() && ((vcfg["ai_special"].str() == "guardian") != u.get_state(unit::STATE_GUARDIAN))) {
		return false;
	}

	if (!vcfg["canrecruit"].blank() && vcfg["canrecruit"].to_bool() != u.can_recruit()) {
		return false;
	}

	if (!vcfg["recall_cost"].blank() && vcfg["recall_cost"].to_int(-1) != u.recall_cost()) {
		return false;
	}

	if (!vcfg["level"].blank() && vcfg["level"].to_int(-1) != u.level()) {
		return false;
	}

	if (!vcfg["defense"].blank() && vcfg["defense"].to_int(-1) != u.defense_modifier(fc_.get_disp_context().map().get_terrain(loc))) {
		return false;
	}

	if (!vcfg["movement_cost"].blank() && vcfg["movement_cost"].to_int(-1) != u.movement_cost(fc_.get_disp_context().map().get_terrain(loc))) {
		return false;
	}

	// Now start with the new WML based comparison.
	// If a key is in the unit and in the filter, they should match
	// filter only => not for us
	// unit only => not filtered
	config unit_cfg; // No point in serializing the unit once for each [filter_wml]!
	for (const vconfig& wmlcfg : vcfg.get_children("filter_wml")) {
			config fwml = wmlcfg.get_parsed_config();
			/* Check if the filter only cares about variables.
			   If so, no need to serialize the whole unit. */
			config::all_children_itors ci = fwml.all_children_range();
			if (fwml.all_children_count() == 1 && 
				fwml.attribute_count() == 1 &&
			    ci.front().key == "variables") {
				if (!u.variables().matches(ci.front().cfg))
					return false;
			} else {
				if (unit_cfg.empty())
					u.write(unit_cfg);
				if (!unit_cfg.matches(fwml))
					return false;
			}
	}

	for (const vconfig& vision : vcfg.get_children("filter_vision")) {
		std::set<int> viewers;

		// Use standard side filter
		side_filter ssf(vision, &fc_);
		std::vector<int> sides = ssf.get_teams();
		viewers.insert(sides.begin(), sides.end());

		bool found = false;
		for (const int viewer : viewers) {
			bool fogged = fc_.get_disp_context().teams()[viewer - 1].fogged(loc);
			bool hiding = u.invisible(loc, fc_.get_disp_context());
			bool unit_hidden = fogged || hiding;
			if (vision["visible"].to_bool(true) != unit_hidden) {
				found = true;
				break;
			}
		}
		if (!found) {return false;}
	}

	if (vcfg.has_child("filter_adjacent")) {
		const unit_map& units = fc_.get_disp_context().units();
		map_location adjacent[6];
		get_adjacent_tiles(loc, adjacent);

		for (const vconfig& adj_cfg : vcfg.get_children("filter_adjacent")) {
			int match_count=0;
			unit_filter filt(adj_cfg, &fc_, use_flat_tod_);

			config::attribute_value i_adjacent = adj_cfg["adjacent"];
			std::vector<map_location::DIRECTION> dirs;
			if (i_adjacent.blank()) {
				dirs = map_location::default_dirs();
			} else {
				dirs = map_location::parse_directions(i_adjacent);
			}

			std::vector<map_location::DIRECTION>::const_iterator j, j_end = dirs.end();
			for (j = dirs.begin(); j != j_end; ++j) {
				unit_map::const_iterator unit_itor = units.find(adjacent[*j]);
				if (unit_itor == units.end() || !filt(*unit_itor, u)) {
					continue;
				}
				boost::optional<bool> is_enemy;
				if (!adj_cfg["is_enemy"].blank()) {
					is_enemy = adj_cfg["is_enemy"].to_bool();
				}
				if (!is_enemy || *is_enemy ==
				    fc_.get_disp_context().teams()[u.side() - 1].is_enemy(unit_itor->side())) {
					++match_count;
				}
			}

			static std::vector<std::pair<int,int> > default_counts = utils::parse_ranges("1-6");
			config::attribute_value i_count = adj_cfg["count"];
			if(!in_ranges(match_count, !i_count.blank() ? utils::parse_ranges(i_count) : default_counts)) {
				return false;
			}
		}
	}

	if (!vcfg["find_in"].blank()) {
		// Allow filtering by searching a stored variable of units
		if (const game_data * gd = fc_.get_game_data()) {
			try
			{
				variable_access_const vi = gd->get_variable_access_read(vcfg["find_in"]);
				bool found_id = false;
				for (const config& c : vi.as_array())
				{
					if(c["id"] == u.id())
						found_id = true;
				}
				if(!found_id)
				{
					return false;
				}
			}
			catch(const invalid_variablename_exception&)
			{
				return false;
			}
		}
	}
	if (!vcfg["formula"].blank()) {
		try {
			const unit_callable main(loc,u);
			game_logic::map_formula_callable callable(&main);
			if (u2) {
				std::shared_ptr<unit_callable> secondary(new unit_callable(*u2));
				callable.add("other", variant(secondary.get()));
				// It's not destroyed upon scope exit because the variant holds a reference
			}
			const game_logic::formula form(vcfg["formula"]);
			if(!form.evaluate(callable).as_bool()) {
				return false;
			}
			return true;
		} catch(game_logic::formula_error& e) {
			lg::wml_error() << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
			// Formulae with syntax errors match nothing
			return false;
		}
	}

	if (!vcfg["lua_function"].blank()) {
		if (game_lua_kernel * lk = fc_.get_lua_kernel()) {
			bool b = lk->run_filter(vcfg["lua_function"].str().c_str(), u);
			if (!b) return false;
		}
	}

	return true;
}
예제 #2
0
파일: pump.cpp 프로젝트: niegenug/wesnoth
	/**
	 * Returns true iff the given event passes all its filters.
	 */
	bool t_pump::filter_event(const event_handler& handler, const queued_event& ev)
	{
		const unit_map *units = resources::units;
		unit_map::const_iterator unit1 = units->find(ev.loc1);
		unit_map::const_iterator unit2 = units->find(ev.loc2);
		vconfig filters(handler.get_config());

		BOOST_FOREACH(const vconfig &condition, filters.get_children("filter_condition"))
		{
			if (!conditional_passed(condition)) {
				return false;
			}
		}

		BOOST_FOREACH(const vconfig &f, filters.get_children("filter_side"))
		{
			side_filter ssf(f, &resources::controller->gamestate());
			if ( !ssf.match(resources::controller->current_side()) )
				return false;
		}

		BOOST_FOREACH(const vconfig &f, filters.get_children("filter"))
		{
			if ( !ev.loc1.matches_unit_filter(unit1, f) ) {
				return false;
			}
		}

		vconfig::child_list special_filters = filters.get_children("filter_attack");
		bool special_matches = special_filters.empty();
		if ( !special_matches  &&  unit1 != units->end() )
		{
			const bool matches_unit = ev.loc1.matches_unit(unit1);
			const config & attack = ev.data.child("first");
			BOOST_FOREACH(const vconfig &f, special_filters)
			{
				if ( f.empty() )
					special_matches = true;
				else if ( !matches_unit )
					return false;

				special_matches = special_matches ||
					              matches_special_filter(attack, f);
			}
		}
		if(!special_matches) {
			return false;
		}

		BOOST_FOREACH(const vconfig &f, filters.get_children("filter_second"))
		{
			if ( !ev.loc2.matches_unit_filter(unit2, f) ) {
				return false;
			}
		}

		special_filters = filters.get_children("filter_second_attack");
		special_matches = special_filters.empty();
		if ( !special_matches  &&  unit2 != units->end() )
		{
			const bool matches_unit = ev.loc2.matches_unit(unit2);
			const config & attack = ev.data.child("second");
			BOOST_FOREACH(const vconfig &f, special_filters)
			{
				if ( f.empty() )
					special_matches = true;
				else if ( !matches_unit )
					return false;

				special_matches = special_matches ||
					              matches_special_filter(attack, f);
			}
		}
		if(!special_matches) {
			return false;
		}

		// All filters passed.
		return true;
	}
예제 #3
0
파일: ssfcreator.cpp 프로젝트: KDE/kimtoy
bool SsfCreator::create(const QString& path, int width, int height, QImage& img)
{
    if (!QFile::exists(path))
        return false;

    KSsf ssf(path);
    if (!ssf.open(QIODevice::ReadOnly))
        return false;

    const KArchiveEntry* entry = ssf.directory()->entry("skin.ini");
    const KArchiveFile* skinini = static_cast<const KArchiveFile*>(entry);

    if (!skinini) {
        entry = ssf.directory()->entry("Skin.ini");
        skinini = static_cast<const KArchiveFile*>(entry);
        if (!skinini)
            return false;
    }

    QFont preEditFont;
    QFont candidateFont;
    QColor preEditColor;
    QColor labelColor;
    QColor candidateColor;

    int pt = 0, pb = 0, pl = 0, pr = 0;
    int zt = 0, zb = 0, zl = 0, zr = 0;

    QByteArray data = skinini->data();

    /// parse ini file content
    bool display = false;
    bool scheme_h1 = false;
    QPixmap skin;
    int hstm = 0;
    int sl = 0, sr = 0;
    int fontPixelSize = 12;
    QString font_ch, font_en;
    unsigned int color_ch, color_en;
    QHash<QString, OverlayPixmap> overlays;
    int opt = 0, opb = 0, opl = 0, opr = 0;
    QColor separatorColor = Qt::transparent;
    int sepl = 0, sepr = 0;

    QTextStream ss(data);
    QString line;
    QString key, value;
    do {
        line = ss.readLine();
        if (line.isEmpty())
            continue;

        if (line.at(0) == '[') {
            display = (line == "[Display]");
            scheme_h1 = (line == "[Scheme_H1]");
            continue;
        }

        if (!line.contains('='))
            continue;

        key = line.split('=').at(0);
        value = line.split('=').at(1);

        if (value.isEmpty())
            continue;

        if (display) {
            if (key == "font_size")
                fontPixelSize = value.trimmed().toInt();
            else if (key == "font_ch")
                font_ch = value;
            else if (key == "font_en")
                font_en = value;
            else if (key == "pinyin_color")
                color_en = value.toUInt(0, 0);
            else if (key == "zhongwen_color")
                color_ch = value.toUInt(0, 0);
        }
        else if (scheme_h1) {
            if (key == "pic") {
                const KArchiveEntry* e = ssf.directory()->entry(value);
                const KArchiveFile* pix = static_cast<const KArchiveFile*>(e);
                if (pix)
                    skin.loadFromData(pix->data());
            }
            else if (key == "layout_horizontal") {
                QStringList list = value.split(',');
                hstm = list.at(0).trimmed().toInt();
                sl = list.at(1).trimmed().toInt();
                sr = list.at(2).trimmed().toInt();
            }
            else if (key == "separator") {
                QStringList list = value.split(',');
                QString sep_color = list.at(0).trimmed();
                separatorColor = sep_color.leftJustified(8, '0').replace("0x", "#");
                sepl = list.at(1).trimmed().toInt();
                sepr = list.at(2).trimmed().toInt();
            }
            else if (key == "pinyin_marge") {
                QStringList list = value.split(',');
                pt = list.at(0).trimmed().toInt();
                pb = list.at(1).trimmed().toInt();
                pl = list.at(2).trimmed().toInt();
                pr = list.at(3).trimmed().toInt();
            }
            else if (key == "zhongwen_marge") {
                QStringList list = value.split(',');
                zt = list.at(0).trimmed().toInt();
                zb = list.at(1).trimmed().toInt();
                zl = list.at(2).trimmed().toInt();
                zr = list.at(3).trimmed().toInt();
            }
            else if (key.endsWith("_display")) {
                QString name = key.left(key.length() - 8);
                overlays.insert(name, OverlayPixmap());
            }
            else if (key.endsWith("_align")) {
                QString name = key.left(key.length() - 6);
                QStringList numbers = value.split(',');
                OverlayPixmap& op = overlays[ name ];
                op.mt = numbers.at(0).toInt();
                op.mb = numbers.at(1).toInt();
                op.ml = numbers.at(2).toInt();
                op.mr = numbers.at(3).toInt();
                op.alignVMode = numbers.at(4).toInt() + numbers.at(5).toInt();    /// FIXME: right or wrong?
                op.alignHMode = numbers.at(6).toInt() + numbers.at(7).toInt();    /// FIXME: right or wrong?
                op.alignArea = numbers.at(8).toInt();
                op.alignTarget = numbers.at(9).toInt();
            }
            else if (overlays.contains(key)) {
                const KArchiveEntry* e = ssf.directory()->entry(value);
                const KArchiveFile* pix = static_cast<const KArchiveFile*>(e);
                if (pix)
                    overlays[ key ].pixmap.loadFromData(pix->data());
            }
        }
    }
    while (!line.isNull());

    /// calculate overlay pixmap surrounding
    QHash<QString, OverlayPixmap>::ConstIterator it = overlays.constBegin();
    QHash<QString, OverlayPixmap>::ConstIterator end = overlays.constEnd();
    while (it != end) {
        const OverlayPixmap& op = it.value();
        switch (op.alignArea) {
            case 1:
                opl = qMax(opl, op.pixmap.width());
                opt = qMax(opt, op.pixmap.height());
                break;
            case 2:
                opt = qMax(opt, op.pixmap.height());
                break;
            case 3:
                opr = qMax(opr, op.pixmap.width());
                opt = qMax(opt, op.pixmap.height());
                break;
            case 4:
                opl = qMax(opl, op.pixmap.width());
                break;
            case 5:
                /// center pixmap, no addition
                break;
            case 6:
                opr = qMax(opr, op.pixmap.width());
                break;
            case 7:
                opl = qMax(opl, op.pixmap.width());
                opb = qMax(opb, op.pixmap.height());
                break;
            case 8:
                opb = qMax(opb, op.pixmap.height());
                break;
            case 9:
                opr = qMax(opr, op.pixmap.width());
                opb = qMax(opb, op.pixmap.height());
                break;
            default:
                /// never arrive here
                break;
        }
        ++it;
    }

    preEditFont.setFamily(font_en);
    preEditFont.setPixelSize(fontPixelSize);
    candidateFont.setFamily(font_ch);
    candidateFont.setPixelSize(fontPixelSize);

    /// swap from bgr to rgb
    preEditColor = QColor(qBlue(color_en), qGreen(color_en), qRed(color_en));
    candidateColor = QColor(qBlue(color_ch), qGreen(color_ch), qRed(color_ch));
    labelColor = candidateColor;

    int pinyinh = QFontMetrics(preEditFont).height();
    int zhongwenh = QFontMetrics(candidateFont).height();
    int pinyinw = QFontMetrics(preEditFont).width("ABC pinyin");
    int zhongwenw = QFontMetrics(candidateFont).width("1candidate");

    /// save target size
    int targetHeight = height;
    int targetWidth = width;

    height = qMax(pt + pinyinh + pb + zt + zhongwenh + zb + opt + opb, skin.height());
    width = qMax(pl + pinyinw + pr, zl + zhongwenw + zr);
    width = qMax(width + opl + opr, targetWidth);
    width = qMax(width, skin.width());

    QPixmap pixmap(width, height);
    pixmap.fill(Qt::transparent);

    QPainter p(&pixmap);

    /// left right
    p.drawPixmap(0, 0, sl, height, skin, 0, 0, sl, skin.height());
    p.drawPixmap(width - sr, 0, sr, height, skin, skin.width() - sr, 0, sr, skin.height());

    /// middle
    QPixmap middlepix = skin.copy(sl, 0, skin.width() - sl - sr, skin.height());
    if (hstm == 0)
        p.drawPixmap(sl, 0, middlepix.scaled(width - sl - sr, height));
    else
        p.drawTiledPixmap(sl, 0, width - sl - sr, height, middlepix.scaled(middlepix.width(), height));

    /// draw overlay pixmap
    it = overlays.constBegin();
    end = overlays.constEnd();
    while (it != end) {
        const OverlayPixmap& op = it.value();
        p.save();
        switch (op.alignArea) {
            case 1:
                p.translate(op.ml, op.mt);
                break;
            case 2:
                if (op.alignHMode == 0) {
                    p.translate((width + opl - opr - op.pixmap.width()) / 2, 0);
                    p.translate(op.ml / 2, op.mt);
                }
                else if (op.alignHMode == 1) {
                    p.translate(opl, 0);
                    p.translate(op.ml, op.mt);
                }
                else if (op.alignHMode == 2) {
                    p.translate(width - opr - op.pixmap.width(), 0);
                    p.translate(-op.mr, op.mt);
                }
                break;
            case 3:
                p.translate(width - opr, 0);
                p.translate(-op.mr, op.mt);
                break;
            case 4:
                if (op.alignVMode == 0) {
                    p.translate(0, (height - opb + opt - op.pixmap.height()) / 2);
                    p.translate(op.ml, op.mt / 2);
                }
                else if (op.alignVMode == 1) {
                    p.translate(0, opt);
                    p.translate(op.ml, op.mt);
                }
                else if (op.alignVMode == 2) {
                    p.translate(0, height - opb - op.pixmap.height());
                    p.translate(op.ml, -op.mb);
                }
                break;
            case 5:
                if (op.alignHMode == 0) {
                    p.translate((width + opl - opr - op.pixmap.width()) / 2, 0);
                    p.translate(op.ml / 2, 0);
                }
                else if (op.alignHMode == 1) {
                    p.translate(opl, 0);
                    p.translate(op.ml, 0);
                }
                else if (op.alignHMode == 2) {
                    p.translate(width - opr - op.pixmap.width(), 0);
                    p.translate(-op.mr, 0);
                }
                if (op.alignVMode == 0) {
                    p.translate(0, (height - opb + opt - op.pixmap.height()) / 2);
                    p.translate(0, op.mt / 2);
                }
                else if (op.alignVMode == 1) {
                    p.translate(0, opt);
                    p.translate(0, op.mt);
                }
                else if (op.alignVMode == 2) {
                    p.translate(0, height - opb - op.pixmap.height());
                    p.translate(0, -op.mb);
                }
                break;
            case 6:
                if (op.alignVMode == 0) {
                    p.translate(width - opr, (height - opb + opt - op.pixmap.height()) / 2);
                    p.translate(-op.mr, op.mt / 2);
                }
                else if (op.alignVMode == 1) {
                    p.translate(width - opr, opt);
                    p.translate(-op.mr, op.mt);
                }
                else if (op.alignVMode == 2) {
                    p.translate(width - opr, height - opb - op.pixmap.height());
                    p.translate(-op.mr, -op.mb);
                }
                break;
            case 7:
                p.translate(0, height - opb);
                p.translate(op.ml, -op.mb);
                break;
            case 8:
                if (op.alignHMode == 0) {
                    p.translate((width + opl - opr - op.pixmap.width()) / 2, height - opb);
                    p.translate(op.ml / 2, -op.mb);
                }
                else if (op.alignHMode == 1) {
                    p.translate(opl, height - opb);
                    p.translate(op.ml, -op.mb);
                }
                else if (op.alignHMode == 2) {
                    p.translate(width - opr - op.pixmap.width(), height - opb);
                    p.translate(-op.mr, -op.mb);
                }
                break;
            case 9:
                p.translate(width - opr, height - opb);
                p.translate(-op.mr, -op.mb);
                break;
            default:
                /// never arrive here
                break;
        }
        p.drawPixmap(0, 0, op.pixmap);
        p.restore();
        ++it;
    }

    if (separatorColor != Qt::transparent) {
        /// draw separator
        int sepy = opt + pt + pinyinh + pb;
        p.drawLine(opl + sepl, sepy, width - opr - sepr, sepy);
    }

    p.translate(opl, opt);
    int y = 0;

    /// draw preedit / aux text
    p.save();
    p.setFont(preEditFont);
    p.setPen(preEditColor);
    y += pt;
    p.drawText(pl, pt, width - pl - pr, pinyinh, Qt::AlignLeft, "ABC pinyin");
    int pixelsWide = QFontMetrics(preEditFont).width("ABC pinyin");
    p.drawLine(pl + pixelsWide, pt, pl + pixelsWide, pt + pinyinh);
    y += pinyinh + pb;
    p.restore();

    /// draw lookup table
    p.save();
    int x = zl;
    y += zt;
    int w = 0;
    p.setFont(candidateFont);
    /// draw label
    p.setPen(labelColor);
    w = p.fontMetrics().width("1");
    p.drawText(x, y, w, zhongwenh, Qt::AlignCenter, "1");
    x += w;
    /// draw candidate
    p.setPen(candidateColor);
    w = p.fontMetrics().width("candidate");
    p.drawText(x, y, w, zhongwenh, Qt::AlignCenter, "candidate");
    x += w;
    p.restore();

    p.end();

    if (targetWidth < width || targetHeight < height) {
        pixmap = pixmap.scaled(targetWidth, targetHeight, Qt::KeepAspectRatio);
    }

    img = pixmap.toImage();

    return true;
}