Подтвердить что ты не робот

Слишком много шрифтов при перечислении с помощью функции EnumFontFamiliesEx

Я пытаюсь создать список шрифтов для пользователя на выбор. Я делаю это, используя EnumFontFamiliesEx, но, к сожалению, список возвращаемых шрифтов слишком длинный. Есть много дополнительных шрифтов, которые кажутся легкомысленными, повторяющимися, для другого языка или иным образом нежелательными для отображения пользователю. Мой скриншот лучше всего иллюстрирует мусор, который я пытаюсь отфильтровать.

Мой код для вызова EnumFontFamiliesEx выглядит следующим образом:

LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lf.lfCharSet = DEFAULT_CHARSET;
// screenDC is result of CreateCompatibleDC(NULL)
EnumFontFamiliesEx(screenDC, &lf, GetFontsCallback, NULL, 0);

Полученный список выглядит следующим образом: после сортировки в алфавитном порядке и удаления шрифтов с именами дубликатов лиц:

enter image description here

Как вы можете видеть, в общем диалоговом окне шрифта ChooseFont отображается очень разумный список шрифтов, который удобен для пользователя и имеет смысл. С другой стороны, мой код отображает длинный список дополнительных шрифтов: шрифты, начинающиеся с "@" (почему? Для чего они нужны?), 3 дополнительных варианта шрифта Arial и несколько других шрифтов неизвестной цели, таких как Aheroni, Andalus, Angsana New, AngsanaUPC и т.д. Это безумие.

Как отфильтровать список шрифтов, возвращаемых EnumFontFamiliesEx, чтобы он точно соответствовал списку, показанному в диалоговом окне ChooseFont?

4b9b3361

Ответ 1

Благодаря Jesse Good, я теперь узнал о некоторых безумных неудачных дизайнерских решениях, сделанных командой Windows 7. Я еще не буду принимать свой собственный ответ, потому что, если кто-то еще придумает способ использования этой скрытой функции шрифта в Windows 7, даже если ключ реестра еще не существует (например, возможно, используя недокументированный API или какой-либо другой обман), и их ответ работает, я соглашусь.

Эта фильтрация выполняется путем фактического "скрытия" шрифтов в панели управления Windows 7. По умолчанию шрифты для других локалей скрыты, но они могут отображаться пользователем. По крайней мере, это идея. На странице MSDN обсуждается эта функция: Международное управление шрифтами.

Вот некоторые ключевые выдержки из этой страницы и других ближайших страниц в MSDN (см. также http://msdn.microsoft.com/en-us/library/windows/desktop/dd371704(v=vs.85).aspx из куковой книги совместимости с Windows 7):

Начиная с Windows 7 инфраструктура управления шрифтами поддерживает скрытие шрифтов, которые не подходят для списков выбора шрифтов пользователя.... Эта функция означает, что пользователям больше не нужно сталкиваться с длинными списками несоответствующих шрифтов.

В Windows 7 нет API-интерфейсов для непосредственного запроса на то, какие шрифты скрыты, или для установки скрытых шрифтов. [выделение мое] Если вы используете Windows SelectFont API (общий диалог Font) чтобы включить выбор шрифтов сегодня, вы получите новое поведение бесплатно. Новая Windows Scenic Ribbon (Font Controls), введенная в Windows 7, также поддерживает это поведение и дает еще одну причину "Ribbonize" ваших приложений.

Когда шрифт выбран в контекст устройства, эффект от рисования не связан с скрытым шрифтом. Функция EnumFontFamiliesEx продолжает перечислять шрифты, которые установлены в скрытые. [focus mine; по-видимому, нет возможности различать скрытые и видимые шрифты с EnumFontFamiliesEx]

Обратите внимание, что кодировки являются устаревшим понятием, соответствующим наборам символов до Unicode. [акцент мой]

ChooseFont будет отображать только отображаемые шрифты и отфильтровывать скрытые шрифты при отображении шрифтов в списке. Добавлен дополнительный флаг (CF_INACTIVEFONTS) в члене флагов структуры CHOOSEFONT, чтобы вы могли отображать все установленные шрифты в списке шрифтов, так же как ChooseFont вел себя до Windows 7.

Иными словами, если вы не используете общий диалог ChooseFont или официальный ленточный элемент управления Windows (доступен только в Windows Vista/7), у вас нет поддерживаемого способа фильтрации скрытых шрифтов. Интересно ли, что многие пользователи в Интернете жалуются, что скрытие шрифтов в панели управления Windows 7, похоже, не имеет эффекта?!? (Я ранее ошибочно писал, что MS Word 2010 отфильтровывает скрытые шрифты. Кажется, это не так, потому что они используют свой собственный ленточный элемент управления, а не ленту, встроенную в Windows. Забавно, что панель управления шрифтами Windows 7, не совместим с одним из флагманских продуктов Microsoft и не может быть совместимым без сброса более мощной ленты в Office.)

Основываясь на ссылке, которую опубликовал Джесси Добрый, я узнал, что скрытые шрифты хранятся в недокументированном ключе реестра. По этой ссылке, а также некоторые эксперименты и анализ с помощью Process Monitor (просмотр как стековых трасс, так и реестров) я узнал следующее:

  • Элемент управления лентой вызывает недокументированную функцию FmsGetFilteredFontList в FMS.DLL(службы управления шрифтами). Его цель кажется совершенно очевидной. Это настоящий позор, который им не нужно было публично документировать и поддерживать.
  • Настройки хранятся в недокументированном разделе реестра, к которому обращается FMS.DLL.
  • Если раздел реестра удален, он обновляется с настройками по умолчанию FmsGetFilteredFontList, которые скрывают шрифты, которые не связаны с текущими языками ввода.
  • Новый профиль пользователя, созданный при чистой установке Windows, не содержит ключей реестра, связанных с тем, какие шрифты должны быть скрыты.

Следовательно, ссылка, размещенная Джесси Доброй, может работать для многих/большинства случаев, но не в 100% случаев. Вам нужен способ надежно воссоздать эти ключи реестра (или, по крайней мере, принять значения по умолчанию), если они не существуют. По умолчанию поведение по-прежнему скрывает некоторые шрифты, даже если ключи реестра исчезли (например, в новом пользовательском профиле).

Ответ 2

Учитывая, что FmsGetFilteredFontList недокументирован, ваши варианты получения точно такого же списка, который пользователь видит в диалоговом окне ChooseFont Windows 7+, могут быть ограничены. Тем не менее, можно получить хорошее приближение к списку шрифтов по умолчанию, используя только документированные API.

Я сделал что-то подобное, чтобы сократить количество возможностей для алгоритма, который автоматически выбирает подходящий шрифт.

Мой подход заключался в использовании битмаски поддиапазона Unicode в FONTSIGNATURE, который может быть проверен при перечислении шрифтов. Если вам известно, какие поддиапазоны Unicode вам нужны, подпись шрифта скажет вам, распространяется ли текущий шрифт. Если это так, включите его в список. Если это не так, то пропустите его. Я подозреваю, что это, вероятно, похоже на то, как FmsGetFilteredFontList создает свой список по умолчанию.

Фокус в том, чтобы выяснить, какие поддиапазоны нужны пользователю. В моем случае это было относительно легко, потому что я точно знал, какой текст мне придется отображать. Я построил отображение поддиапазонов в значениях битмаски в стиле FONTSIGNATURE на основе документации.

Я просмотрел точки кода в тексте, который будет отображаться, просматривая их в сопоставлении и создавая целевую битовую маску. Я побитовал и использовал эту целевую битовую маску с таковой в подписи шрифта для каждого перечислимого шрифта. Всякий раз, когда результат соответствовал целевой битовой маске, я знал, что шрифт может (скорее всего) поддерживать текст. Для моего приложения я потребовал, чтобы все целевые биты присутствовали в шрифте. Для вашего приложения, я думаю, вы хотите любой из целевых бит.

Базовая маска для шрифтов является хорошим первым разрезом для символов, которые предоставляет шрифт. Вы можете использовать GetFontUnicodeRanges, чтобы быть абсолютно уверенным, но я обнаружил, что это не было необходимо, и оно также было медленнее, чем просто проверка подписи шрифтов.

В вашем случае, возможно, у вас будет определенная текстовая строка, доступная на языке пользователя. Например, из документа, который они редактируют, или из ресурса пользовательского интерфейса, который был переведен. Вы можете сканировать этот образец текста, чтобы получить подпись целевого шрифта.

Например, если вы сканируете текст на английском языке, вы обнаружите, что все необходимые символы находятся в латинском поддиапазоне. Если вы посмотрите на апплет панели управления шрифтами в Windows 7 для английского пользователя (и переключитесь на представление деталей), вы увидите, что столбец Показать/скрыть сильно коррелирован с тем, включен ли латинский алфавит в столбце Разработано для, который представляется текстовым представлением макета поддиапазона подписи юникода шрифта.

Обновление. Я просто попытался перечислить шрифты с помощью DirectWrite, считая, что этот новый API может обрабатывать функцию скрытия шрифтов. Увы, он возвращает все и не имеет параметров (которые я могу найти) для фильтрации скрытых шрифтов.

Ответ 3

Позорно, что Microsoft не документировала эту функциональность, если честно, но все больше и больше это то, что мы ожидаем от них.

Другой способ фильтрации собственного списка шрифтов - использовать оболочку, перечислив папку шрифтов. Если вы посмотрите в "Проводнике", вы увидите, что скрытые шрифты отображаются с помощью призрак - мы можем использовать этот атрибут, чтобы определить, скрыт ли этот шрифт.

Например (не полный):

LPITEMIDLIST pidlFonts;
if (SUCCEEDED(SHGetKnownFolderIDList(FOLDERID_Fonts, 0, nullptr, &pidlFonts)))
{
    CComPtr<IShellFolder> psf;
    if (SUCCEEDED(SHBindToObject(nullptr, pidlFonts, nullptr, IID_IShellFolder, reinterpret_cast<void**>(&psf))))
    {
        CComPtr<IEnumIDList> pEnum;
        if (SUCCEEDED(psf->EnumObjects(hWnd, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN | SHCONTF_INIT_ON_FIRST_NEXT, &pEnum)))
        {
            LPITEMIDLIST pidl;
            ULONG celt = 0;
            while (pEnum->Next(1, &pidl, &celt) == S_OK)
            {
                SFGAOF hidden = SFGAO_GHOSTED;
                if (SUCCEEDED(psf->GetAttributesOf(1, const_cast<LPCITEMIDLIST*>(&pidl), &hidden)) && (hidden & SFGAO_GHOSTED) == SFGAO_GHOSTED)
                {
                    // this font should be hidden
                    // get its name via IShellFolder::GetDisplayNameOf
                }
                CoTaskMemFree(pidl);
            }
        }
    }
    CoTaskMemFree(pidlFonts);
}

Вы можете использовать этот метод для создания набора скрытых шрифтов, а затем использовать его для фильтрации результатов EnumFontFamiliesEx.

Ответ 4

Я думаю, что вся дискуссия здесь вводит в заблуждение.

Когда я предлагаю селектор шрифтов для моих пользователей, почему мне следует беспокоиться о том, какие шрифты скрыты от Microsoft? И почему я должен скрывать все шрифты, которые Microsoft считает скрытыми по умолчанию?

Что делать, если мой пользователь хочет использовать только один из тех шрифтов, которые Microsoft скрыла? Я поставил бы бремя на моего пользователя на панель управления, чтобы скрыть этот шрифт?

Что, если в какой-то день китайский пользователь хочет написать китайский текст на английской Windows и китайский шрифт скрыт?

Я думаю, что есть намного лучший способ ограничить большое количество шрифтов, возвращенных EnumFontFamiliesEx().

Я написал свой собственный селектор шрифтов, который имеет фильтр шрифтов, который позволяет пользователю выбирать группу шрифтов, которые он хочет использовать. Таким образом, я ничего не скрываю и отдаю все полномочия пользователю, а не Microsoft!

Пользователь может ХОТЯТ видеть ВСЕ Шрифты! Иногда нужно просто Arial Black или Arial Narrow, хотя Microsoft считает, что он должен быть скрыт.

int CALLBACK EnumFontFamExProc(const LOGFONT* pk_Font, 
                               const TEXTMETRIC* pk_Metric, 
                               DWORD e_FontType, 
                               LPARAM lParam)
{
    if (e_FontType & TRUETYPE_FONTTYPE)
    {   
        // u32_Flags128 = DWORD[4] = 4 * 32 bit = 128 bit
        DWORD* u32_Flags128 = ((NEWTEXTMETRICEX*)pk_Metric)->ntmFontSig.fsUsb;

        if (u32_Flags128[13 / 32] & (1 << (13 % 32)))
        {
            // the font contains arabic characters (bit 13)
        }
        if (u32_Flags128[38 / 32] & (1 << (38 % 32)))
        {
            // the font contains mathematical symbols (bit 38)
        }
        if (u32_Flags128[70 / 32] & (1 << (70 % 32)))
        {
            // the font contains tibetan characters (bit 70)
        }
    }

В обратном вызове вы получаете флаг 128 бит, который точно определяет, какие области Юникода поддерживаются шрифтом.

См. http://msdn.microsoft.com/en-us/library/dd374090%28v=vs.85%29.aspx

Вы можете использовать эти 128 бит для фильтрации и уменьшения количества шрифтов, отображаемых в списке шрифтов:

enter image description here