• 0
Вадим Смоленский

Вертикальное смещение юникодных шрифтов

Вопросы

В своем проекте (это японско-русский словарь) мне приходится выводить на TImage.Bitmap.Canvas хранящиеся в юникоде иероглифы, причем шрифт пользователь может выбрать сам. Столкнулся с неприятным явлением: разные шрифты располагают выводимый символ на разной высоте, в результате чего иероглиф часто не вписывается в предназначенную для него область. По умолчанию принят шрифт Tahoma, с ним всё хорошо:

JI_tahoma.png.756f1d23bc94c79ad622f20e25f44271.png

Но вот шрифт Yu Mincho:

JI_Yu_Mincho.png.f63e2b85d50bf8ae202b587c7b30f652.png

А вот, еще хлеще, Kozuka Gothic:

JI_Kozuka_Gothic.png.1856d8711b3cd526b42c278ddf699957.png

Вопрос: что за параметр регулирует вертикальное смещение, можно ли его вынуть и с ним работать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

37 ответов на этот вопрос

  • 0
48 минут назад, dnekrasov сказал:

Попробуйте сместить вверх на TTextMetric.tmExternalLeading

Компилятору не нравится, говорит: method identifier expected.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
5 минут назад, Вадим Смоленский сказал:

Компилятору не нравится, говорит: method identifier expected

А как Вы его используете? TextMetric надо предварительно загрузить для выбранного шрифта API-шными функциями (посмотрите FMX.FontGlyphs.Win.TWinFontGlyphManager.LoadResource)

Изменено пользователем dnekrasov

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
4 минуты назад, dnekrasov сказал:

посмотрите FMX.FontGlyphs.Win.TWinFontGlyphManager.LoadResource

Простите мне мое невежество: это где нужно смотреть? В хелпах? На сайте Embarcadero? Где-нибудь еще?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 минуты назад, Вадим Смоленский сказал:

это где нужно смотреть

Это использование WinAPI. В частности функция GetTextMetrics. Смотреть лучше всего в MSDN

В модуле FMX.FontGlyphs.Win в методе TWinFontGlyphManager.LoadResource просто хорошо видно как с ней работать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
2 часа назад, dnekrasov сказал:

В модуле FMX.FontGlyphs.Win в методе TWinFontGlyphManager.LoadResource просто хорошо видно как с ней работать.

Изучив упомянутый метод в указанном модуле, сделал вывод, что код должен выглядеть примерно так:

function FontVertShift (F: TFont): Single;
var Metrics: TTextMetric;
    DC: HDC;
    HF: HFont;
begin
  HF:=SelectObject(DC, F.Handle);
  SelectObject(DC, HF);
  GetTextMetrics(DC, Metrics);
  result := Metrics.tmExternalLeading
end;

Затык в том, что FireMonkey не предоставляет Handle для шрифта. Как же передать сюда шрифт?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, Вадим Смоленский сказал:

Как же передать сюда шрифт?

Как-то так:

function FontVertShift (F: TFont): Single;
var
  Metrics: TTextMetric;
  DC: HDC;
  HF: HFont;
begin
  DC := CreateCompatibleDC(0);
  try
    HF := CreateFont(-Round(F.Size * GetDCScale(DC)), 0, 0, 0, FontWeightToWinapi(F.StyleExt.Weight),
      Cardinal(not F.StyleExt.Slant.IsRegular),
      0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
      CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
      DEFAULT_PITCH or FF_DONTCARE, PChar(F.Family));
    if HF = 0 then
      Exit(0);

    try
      SelectObject(DC, HF);
      GetTextMetrics(DC, Metrics);
    finally
      DeleteObject(HF);
    end;

    Result := Metrics.tmExternalLeading
  finally
    DeleteDC(DC);
  end;
end;

 

Изменено пользователем dnekrasov

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Кстати, тут, скорее всего, надо еще и учитывать и Metrics.tmInternalLeading

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
28 минут назад, dnekrasov сказал:

Как-то так:

Запускается, но tmExternalLeading на любом шрифте возвращает 0.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Вот, кстати, tmInternalLeading больше похоже на то, что я ищу. Он недостаточно смещает, но пропорционально. Попробую подобрать коэффициент...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Выложите шрифты, которые используете и кусочек кода, где задаёте размер, стиль и как выводите.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Код самый простой:

with MyImage.Bitmap.Canvas do
begin
 BeginScene;
 Font.Family := 'Tahoma';
 Font.Style := [];
 Font.Size := 60;
 FillText (RectF (0, 0-FontVertShift(Font), Width-1, Height-1), '字' , False , 1 , [] , TTextAlign.Leading, TTextAlign.Leading);
 EndScene;
end;

Код функции брал ваш, только tmExternalLeading заменил на  tmInternalLeading.

Шрифты пробовал такие (после названия - сначала значение tmInternalLeading, затем tmExternalLeading):

MS Mincho:  0, 0  (проблемы нет)
MS Gothic:  0, 0  (проблемы нет)
Tahoma:  12, 0  (в самый раз)
Yu Gothic:  17, 19  (недостаточно)
Kozuka Gothic Pr6N B:  50,0  (недостаточно)
Meiryo:  30, 0  (чересчур)

За ссылку спасибо, заглянул - но там, как я понял, материал для разработчиков шрифтов. Мои амбиции так далеко не простираются.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Вообще, судя по спецификации, смещение надо вычислять так

Metrics.tmAscent + Metrics.tmDescent + Metrics.tmExternalLeading - F.Size

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Почти для всех вышеперечисленных шрифтов по этой формуле получается как раз значение tmInternalLeading. Единственное исключение - Yu Gothic, для него получилось 36, против прежнего 17. Было недостаточно, стало чересчур. Подход по-прежнему работает только для шрифта Tahoma.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

@Вадим Смоленский, попробуйте для шрифта Yu Gothic рассчитывать смещение как Font.Size * 0.408576 а для Kozuka Gothic Pr6N B - Font.Size * 1.0033 (мне просто интересно - правильно ли я рассчитал коэффиценты)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

Да, с этими коэффициентами на этих шрифтах выходит отлично! Что же тут за калькуляция?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
7 часов назад, Вадим Смоленский сказал:

Да, с этими коэффициентами на этих шрифтах выходит отлично!

Во как! Значит GetTextMetrics неправильно рассчитывает tmExternalLeading  и tmInternalLeading.

7 часов назад, Вадим Смоленский сказал:

Что же тут за калькуляция?

Сам расчет описан здесь

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0

@Вадим Смоленский, а можно как-то увидеть Ваш проект - а то дочка японский учит - сказала, что хороший словарик всегда пригодится :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
1 час назад, dnekrasov сказал:

@Вадим Смоленский, а можно как-то увидеть Ваш проект - а то дочка японский учит - сказала, что хороший словарик всегда пригодится :)

Да, конечно. Это ЯРКСИ (японско-русский компьютерный словарь иероглифов). Если ваша дочка про него еще не слышала, то вот он где:

http://www.susi.ru/yarxi

Там есть версии для мобильных платформ тоже. Версия 7.7 для Windows, которая вывешена, разрабатывалась еще в Delphi 6, которой я продолжал пользоваться до 2016 года. Сейчас перешел наконец в современную программную среду и целый год потратил на переделку интерфейса. Идет бета-тестирование; надеюсь через неделю-другую вывесить версию 8.0, которая будет несравнимо лучше. Так что есть смысл со скачиванием немножко подождать.

Документацию изучаю, но пока не смог понять, как именно нужно действовать...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
18 минут назад, Вадим Смоленский сказал:

Если ваша дочка про него еще не слышала

Говорит, что слышала - однокурсники нахваливают, а преподаватели советуют.

 

20 минут назад, Вадим Смоленский сказал:

Документацию изучаю, но пока не смог понять, как именно нужно действовать

Посмотрите раздел "Baseline to Baseline Distances". Метрики шрифта я вытягивал с помощью редактора FontForge - как это сделать программно - не знаю, надо досконально разбираться в формате.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
35 минут назад, dnekrasov сказал:

Посмотрите раздел "Baseline to Baseline Distances".

Мало что можно понять из этого раздела. Вот, дают формулу для external leading:

MAX( 0, LineGap - ((usWinAscent + usWinDescent) - (Ascender - Descender)))

Что такое LineGap? Какая разница между usWinAscent и Ascender? Как они оба соотносятся с нашим Metrics.tmAscent?

Трудно поверить, что я один во всем интернете столкнулся с этой проблемой. Где-то все-таки должен быть готовый алгоритм.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
33 минуты назад, Вадим Смоленский сказал:

Что такое LineGap? Какая разница между usWinAscent и Ascender?

Про это можно почитать здесь

42 минуты назад, Вадим Смоленский сказал:

Как они оба соотносятся с нашим Metrics.tmAscent

Как я понял:

Metrics.tmAscent = unitsPerEm / Font.Size * uWinAscend

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
17 минут назад, dnekrasov сказал:

Про это можно почитать здесь

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

Да, если у вашей дочки есть телефон с андроидом, то буду рад выписать ей бесплатную лицензию на полную андроидную версию ЯРКСИ. Все-таки вы мне помогли, в какой-то мере прояснили вопрос. Пишите на vsСОБАКАsusi.ru

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты
  • 0
6 часов назад, Вадим Смоленский сказал:

Там есть версии для мобильных платформ тоже.

Для мобильных платформ тоже на Delphi сделано?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

Войти

  • Похожий контент

    • От Вадим Смоленский
      В феврале я сетовал, что мое Windows-приложение не хочет нормально запускаться в Linux под Wine 3.0. Но время идет, вышел Wine 3.6 - и теперь один из тестировщиков радостно сообщил, что всё заработало, за вычетом одного досадного момента. А именно: приложение способно сохранять фокус ввода лишь долю секунды, потом теряет. Соответственно, невозможно ничего ввести в текстовые боксы, разве только одну-две буквы. Можно щелкнуть по заголовку приложения, оно опять получит фокус - и через мгновение снова потеряет. Куда именно при этом переходит фокус, непонятно. Тестировщик утверждает, что только мое приложение ведет себя так, все остальные работают нормально.
      Нет ли у кого-нибудь идей? Что нужно проверить?
    • От Вадим Смоленский
      В декабре я задавал здесь вопрос о борьбе с перехватами нажатий клавиш компонентом TWebBrowser. Продвинутый пользователь Kami посоветовал тогда, раз уж меня интересует только Windows, поставить хук на клавиатуру. Поделился полезной ссылкой. Добавил, что можно еще много чего нагуглить. Что-то действительно нагуглилось - но не в том объеме, чтобы я смог четко понять, как это следует делать. Вопросов много. Куда именно должна быть воткнута функция KeyboardProc? Что в ней должно содержаться, чтобы управление передавалось уже написанному обработчику события FormKeyDown? Многие также упоминают о возникающих проблемах с юникодом, и хорошо было бы понять, как уберечься от них.
      Буду очень признателен, если кто-нибудь осветит эту темную для меня материю.
    • От Вадим Смоленский
      Как преобразовать тип HString в обычную строку?
      Нагуглил упоминания о функции TWindowsString.HStringToString, которая должна находиться в System.WinrtHelpers. Но такого юнита в поставке Delphi не наблюдаю. Может, его можно где-нибудь раздобыть? Или существуют иные способы?
    • От Вадим Смоленский
      Пытаясь разобраться с проблемой размещения файлов в UWP-приложениях, пришел к необходимости создать в своем коде объект класса ApplicationData. Этот класс описан на соответствующей странице майкрософтовской документации, где в самом начале обозначено следующее:
      Namespace:  Windows.Storage
      Assemblies:  Windows.Storage.dll, Windows.dll
      Юнита с именем Windows.Storage или Winapi.Windows.Storage я в поставке Delphi не наблюдаю. Неудивительно, что попытки вставить соответствующие идентификаторы в раздел uses ни к чему не ведут. Как в таких случаях поступают? Откуда берут необходимое?
    • От Вадим Смоленский
      Хочу еще раз обратиться к коллективному разуму в надежде все-таки разобраться с тем, как должен быть устроен пакет appx для размещения в Microsoft Store. А именно - как организовать размещение настроек и пользовательских файлов в специально отведенных для этого папках, а не в установочном каталоге, что запрещено.
      Вся информация, которую я смог к сегодняшнему дню накопать, размещена на этой странице майкрософтовской документации и сводится к тому, что при инсталляции пакета appx автоматически создается хранилище пользовательских данных из трех папок. Цитирую: one for local files, one for roaming files, and one for temporary files. В общем-то, это удобно - по крайней мере, не нужно заботиться о деинсталляции: ровно эти же папки при удалении программы и сотрутся.
      Вопрос в том, как к этому хранилищу обратиться. В идеале, конечно же, хотелось бы иметь возможность уже в Deployment Manager обозначить для некоторых файлов, что они должны быть положены в это хранилище, а не в установочный каталог. Но как это можно сделать и можно ли сделать вообще, мне понять не удалось. Списка констант для параметра Remote Path я найти не смог, а отдельные упоминаемые тут и там константы ('res', 'assets', 'classes', 'library') явно относятся к мобильным платформам, а не к Windows.
      Если так поступить нельзя, остается класть всё в установочный каталог, а при первом запуске приложения переносить в нужное место. Но опять же: как приложению заполучить полный адрес этого места? Ведь это вовсе не привычные нам CSIDL_APPDATA и не CSIDL_COMMON_APPDATA, это нечто новое, доселе невиданное, в документации называемое "local app data store". На той же странице есть пример соответствующего кода с использованием класса ApplicationData, но он написан на незнакомом мне C#. Там есть также ссылки на описание класса ApplicationData, но по ссылкам тоже C# и C++. Был бы очень признателен, если бы кто-нибудь показал мне, как заполучить адрес local app data store средствами Delphi.
    • От Вадим Смоленский
      Целый месяц бодаюсь с Microsoft Store, пытаясь разместить у них свое UWP-приложение в виде пакета appx. Получаю отлуп за отлупом. Проблема: приложение должно располагать папкой для пользователя, отличной от установочного каталога, где он мог бы сохранять файлы. Там же должны храниться файлы настроек. Раньше, создавая дистрибутив в InnoSetup, я всегда предусматривал создание такой папки по адресу <user>\AppData\Roaming\MyApp. Теперь всё полагается указывать в разделе Deployment - но как там указать такой адрес? Вроде бы, есть графа "Remote path", и логично существовать каким-то макросам для нее, вроде AppData - но никакой информации на эту тему я найти не смог. Видел лишь упоминания таких макросов, как res, assets, classes, library - однако все они, как я понял, относятся к мобильным платформам.
      Попытки обходных маневров не удались. Сначала я решил всё класть в установочный каталог, а при первом запуске создавать нужную папку и перебрасывать в нее несколько файлов. На моем компьютере это работает, но при тестировании в Microsoft Store отчего-то валится (присылают скриншот с сообщением "Access is denied"). То ли нельзя стирать файлы в установочном каталоге, то ли нельзя в таком режиме создавать новый каталог. Потом я прочитал где-то, что UWP-приложениям всё равно, где лежат файлы, они могут их менять даже в установочном каталоге. Попробовал всё валить в одну кучу и так работать. Увы, опять отрапортовали о падении ("The app tries to create a file under WindowsApps folder").
      Получается, что все-таки нужно как-то заставить appx-дистрибутив создавать при установке папку по адресу <user>\AppData\Roaming\MyApp. Но как?
      Спрашивал на experts-exchange, там знатоков не нашлось. Если и здесь нет, может, кто-нибудь хотя бы подскажет, в каких местах есть смысл спросить?
    • От Вадим Смоленский
      Проект для Windows, автономная БД SQLite, никаких клиент-серверных дел, компоненты и операции самые простые: SQL-запрос в TFDQuery, вызов метода Open. Всё работает нормально, но отдельные пользователи жалуются на эпизодические непредсказуемые падения при поиске. Интересно, что после каждого такого падения всё опять функционирует нормально, но потом приложение не удается нормально закрыть, приходится вызывать диспетчер задач.
      Стабильно воспроизвести не могут ни пользователи, ни я сам. Мне удалось это считанные несколько раз - я лишь смог убедиться через отладчик, что проблема при закрытии связана именно с базой данных: вставлял в обработчик FormCloseQuery оператор TFDConnection.Close - и программа пару раз упала именно на этом операторе.
      Найти корень зла пока не удалось. Показалось только, что проблема возникает лишь тогда, когда поиск приводит к слишком большому (несколько тысяч) числу записей в TFDQuery.
      Может, стоит поменять какие-нибудь установки в TFDConnection или TFDQuery ?
    • От Вадим Смоленский
      Упаковываю свое приложение в appx при помощи кнопки Deploy в Deployment Manager. При этом в разделе Опций "Manifest File" выставлено "Auto Generate". В итоге получается файл AppxManifest.xml; насколько я понимаю, этот манифест также включается в состав итогового пакета appx.
      Пробую загрузить получившийся appx в Microsoft Store. Грузится очень долго и в итоге выдает ошибку: "You don't have permissions to specify the following namespaces in the appx manifest file of the package MyApp.appx: restricted namespace: http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
      В файле AppxManifest.xml действительно отыскивается такой фрагмент. Убираю его, заодно убираю фрагмент  <Capabilities> ... </Capabilities> (если не убрать, получившийся xml даже не отобразится в браузере), переименовываю в MyApp.manifest, меняю установку для манифеста на "Custom", указываю имя. Всё повторяю. Результат ровно тот же.
      Полностью меняю содержимое MyApp.manifest, беря шаблон отсюда. Всё повторяю. Результат опять тот же. Опять магазину не нравится фрагмент манифеста насчет restricted capabilities, которого теперь, по идее, там быть не должно.
      Такое ощущение, что при формировании пакета appx не имеют никакого значения установки по поводу манифеста. Он всегда запихивается в appx в некоем дефолтном варианте, повлиять на который невозможно.
      Или все-таки возможно?
    • От Вадим Смоленский
      При упаковке Windows-приложения в appx все дополнительные файлы приходится класть в одну папку с исполнимым файлом, ибо Deployment Manager, судя по всему, не предусматривает возможности сразу положить их в AppData. Но я все-таки хочу, чтобы некоторые файлы были легко доступны пользователям, поэтому организовал приложение так, что оно при первом запуске создает новый каталог в AppData:
      MyDirectory:=GetSpecialFolderPath(CSIDL_APPDATA)+'\MyApp'; CreateDir(MyDirectory); TDirectory.SetAttributes(MyDirectory,[TFileAttribute.faNormal]); После этого в созданный каталог переносятся некоторые файлы, и всё работает хорошо, за исключением одного момента: этот новый каталог невозможно открыть, например, в Проводнике. Из самого приложения легко можно открыть диалоговое окно и увидеть в нем этот каталог и все файлы; можно их читать и в них писать, но вне приложения этот каталог невидим. Всё равно что не существует.
      Мне казалось, что присвоение каталогу атрибута faNormal дожно все проблемы решить. Увы, не решает. В чем тут закавыка?
    • От Вадим Смоленский
      Устанавливая и запуская свое Windows-приложение, предназначенное для Microsoft Store и запакованное в appx, наткнулся на интересный феномен. Чтобы узнать адрес текущего каталога, я всегда использовал функцию SysUtils.GetCurrentDir. Полученный полный адрес был мне нужен, например, чтобы показывать в TWebBrowser файлы хелпов (относительные адреса там почему-то не прокатывают). Раньше адресом текущего каталога всегда был адрес, где лежит исполнимый файл - условно говоря, C:\Program Files\MyApp. Теперь, когда пакет создается по принципам UWP,  исполнимый файл и прочее хозяйство кладется в каталог C:\Program Files\WindowsApps\MyApp_1.0.0.0_x86__sp51hrchc9zqj.  При этом функция GetCurrentDir почему-то возвращает совершенно другой адрес, а именно C:\WINDOWS\system32. Соответственно, TWebBrowser ничего не показывает.
      Функция SysUtils.GetDir ведет себя так же.
      Как быть?
  • Последние посетители   0 пользователей онлайн

    Ни одного зарегистрированного пользователя не просматривает данную страницу