procs/gc.js
function filterFeature(table, options) {
	if (!table) return;
	options = options || {};
	const visibleLanguages = new Set();
	const visibleFeatures = new Set();
	const visibleLookups = new Set();
	for (let lid in table.languages) {
		const lang = table.languages[lid];
		if (!lang) continue;
		if (options.isValidLang && !options.isValidLang(lid, lang)) continue;
		visibleLanguages.add(lid);
		if (lang.requiredFeature && table.features[lang.requiredFeature]) {
			if (
				!(
					options.isValidFeature &&
					!options.isValidFeature(
						lang.requiredFeature,
						table.features[lang.requiredFeature]
					)
				)
			) {
				visibleFeatures.add(lang.requiredFeature);
			}
		}
		if (!lang.features) lang.features = [];
		for (let f of lang.features) {
			if (!table.features[f]) continue;
			if (options.isValidFeature && !options.isValidFeature(f, table.features[f])) continue;
			visibleFeatures.add(f);
		}
	}
	table.languages = kvfilter(table.languages, k => visibleLanguages.has(k));
	table.features = kvfilter(table.features, k => visibleFeatures.has(k));
	for (let fid in table.features) {
		if (!table.features[fid]) continue;
		for (let lutid of table.features[fid]) {
			if (!table.lookups[lutid]) continue;
			visibleLookups.add(lutid);
		}
	}
	do {
		const nA = visibleLookups.size;
		for (let lid in table.lookups) {
			const lut = table.lookups[lid];
			if (!lut || !visibleLookups.has(lid)) continue;
			switch (lut.type) {
				case "gsub_chaining":
				case "gpos_chaining":
					for (let rule of lut.subtables) {
						for (let application of rule.apply) {
							visibleLookups.add(application.lookup);
						}
					}
					break;
				default:
					break;
			}
		}
		const nK = visibleLookups.size;
		if (nK >= nA) break;
	} while (true);
	table.lookups = kvfilter(table.lookups, k => visibleLookups.has(k));
}
function filterFeature(table, options) {
	if (!table) return;
	options = options || {};
	const visibleLanguages = new Set();
	const visibleFeatures = new Set();
	const visibleLookups = new Set();
	for (let lid in table.languages) {
		const lang = table.languages[lid];
		if (!lang) continue;
		if (options.isValidLang && !options.isValidLang(lid, lang)) continue;
		visibleLanguages.add(lid);
		if (lang.requiredFeature && table.features[lang.requiredFeature]) {
			if (
				!(
					options.isValidFeature &&
					!options.isValidFeature(
						lang.requiredFeature,
						table.features[lang.requiredFeature]
					)
				)
			) {
				visibleFeatures.add(lang.requiredFeature);
			}
		}
		if (!lang.features) lang.features = [];
		for (let f of lang.features) {
			if (!table.features[f]) continue;
			if (options.isValidFeature && !options.isValidFeature(f, table.features[f])) continue;
			visibleFeatures.add(f);
		}
	}
	table.languages = kvfilter(table.languages, k => visibleLanguages.has(k));
	table.features = kvfilter(table.features, k => visibleFeatures.has(k));
	for (let fid in table.features) {
		if (!table.features[fid]) continue;
		for (let lutid of table.features[fid]) {
			if (!table.lookups[lutid]) continue;
			visibleLookups.add(lutid);
		}
	}
	do {
		const nA = visibleLookups.size;
		for (let lid in table.lookups) {
			const lut = table.lookups[lid];
			if (!lut || !visibleLookups.has(lid)) continue;
			switch (lut.type) {
				case "gsub_chaining":
				case "gpos_chaining":
					for (let rule of lut.subtables) {
						for (let application of rule.apply) {
							visibleLookups.add(application.lookup);
						}
					}
					break;
				default:
					break;
			}
		}
		const nK = visibleLookups.size;
		if (nK >= nA) break;
	} while (true);
	table.lookups = kvfilter(table.lookups, k => visibleLookups.has(k));
}
markFeature