Разрабатываю библиотеку по конвертации EMF файлов в PDF документ и появились вопросы как WinAPI интерпретирует данные TrueType шрифтов.
В TrueType шрифтах есть таблица hmtx, которая хранит advance width и left-side bearing (LSB) в design юнитах. Для преобразования design units -> device units пользуюсь формулой
В WinAPI есть функция GetCharABCWidths(), которая возвращает в структуре ABC параметры глифов в виде набора
abcA — ширина отступа перед отрисовкой глифа, LSB в терминах TrueType;
abcB — ширина глифа, вычисляется как (xmax — xmin) из таблицы glyf;
abcC — ширина отступа после отрисовки глифа, вычисляется как advance width — LSB — (xmax — xmin);
В итоге полная ширина глифа равна сумме трех составляющих abcA + abcB + abcC и она должна равняться advance width из таблицы hmtx. Но она отличается на 1-2 логических юнита, что приводит к наложению соседних слов в PDF документе, когда в EMF все корректно. Например,
здесь черным цветом — расположение символов в EMF документе, зеленым — расположение в PDF документе.
Может кто подскажет, что упустил из расчетов? Книгу "Windows Graphics Programming Win32 GDI and DirectDraw" Feng Yuan смотрел, там про расчеты ничего не сказано.
Здравствуйте, Hydrophobia, Вы писали:
H>Может кто подскажет, что упустил из расчетов? Книгу "Windows Graphics Programming Win32 GDI and DirectDraw" Feng Yuan смотрел, там про расчеты ничего не сказано.
Device units are always rounded to the nearest pixel. The propagated round-off error can become very large, especially when an application is working with screen sizes.
Device units are always rounded to the nearest pixel. The propagated round-off error can become very large, especially when an application is working with screen sizes.
округление присутствует, использую std::lround(). В wine что-то похожее используют.
Здравствуйте, Hydrophobia, Вы писали:
H>округление присутствует, использую std::lround(). В wine что-то похожее используют.
Вывод в PDF идёт по словам или по буквам?
Очевидно, что начало второго слова посчитано правильно.
Я бы попробовал вычислять X для каждой буквы отдельно, что бы не накапливать ошибки округления.
картинка из топика — вывод по словам.
VF>Очевидно, что начало второго слова посчитано правильно. VF>Я бы попробовал вычислять X для каждой буквы отдельно, что бы не накапливать ошибки округления.
да, так сделал, получилось весьма пригодно, но все равно местами межсимвольное расстояние скачет. С другой стороны в EMF есть текстовые записи (EMR_EXTTEXTOUT[A, W]), в которых есть информация по межсимвольному расстоянию (массив EMREXTTEXTOUTW::emrtext::offDx). Если EMF базируется на устройстве с низким DPI (например, экран), то вывод текста в PDF очень низкого качества:
слово "within" разбивается на две части, причина — большая разница в ширине глифа 'w'.
Здравствуйте, Hydrophobia, Вы писали:
H>Если EMF базируется на устройстве с низким DPI (например, экран), то вывод текста в PDF очень низкого качества
Решал я похожую задачу в одном из своих WYSIWYG редакторов. Но не решил до конца ибо она решения не имеет в общем случае.
В EMF (т.е. GDI) глифы приколочены к пиксельным сеткам. PDF же оперирует векторами.
MS Word например для вывода текста на экран не использует примитивы GDI (типа TextOut например) в чистом виде.
Позиции глифов там float numbers, а не integer как в GDI.
Задача имеет квази-оптимальное решение только если ты сам осуществляешь EMF генерацию и выставляешь большое разрешение — 300 DPI как минимум.
Для произвольных же EMF сделанных в масштабе экрана — увы и ах.
CS>Задача имеет квази-оптимальное решение только если ты сам осуществляешь EMF генерацию и выставляешь большое разрешение — 300 DPI как минимум. CS>Для произвольных же EMF сделанных в масштабе экрана — увы и ах.
Генерация EMF осуществляется самостоятельно, но не нашел способа выставить DPI так, чтобы вызовы типа GetDeviceCaps() вовзращали установленное DPI.
А "виртуальное" DPI выставляю так
Здравствуйте, Hydrophobia, Вы писали:
H>Здравствуйте, c-smile, Вы писали:
CS>>Задача имеет квази-оптимальное решение только если ты сам осуществляешь EMF генерацию и выставляешь большое разрешение — 300 DPI как минимум. CS>>Для произвольных же EMF сделанных в масштабе экрана — увы и ах.
H>Генерация EMF осуществляется самостоятельно, но не нашел способа выставить DPI так, чтобы вызовы типа GetDeviceCaps() вовзращали установленное DPI. H>А "виртуальное" DPI выставляю так
Ну тогда я бы просто размеры и позиции всех выводимых GDI примитивов (в т.ч. font sizes) умножал на 100 и в EMF имел бы fixed float координаты —
float pdfPos = emfPos / 100.0f;
Как-то так в общем.
А что ты используешь в качестве PDF canvas? Что-то свое или библиотеку какую?
CS>Ну тогда я бы просто размеры и позиции всех выводимых GDI примитивов (в т.ч. font sizes) умножал на 100 и в EMF имел бы fixed float координаты — CS>
CS>float pdfPos = emfPos / 100.0f;
CS>
CS>Как-то так в общем.
видимо обманул, генерация EMF осуществляется внешней программой и она для рассчетов использует GetDeviceCaps(). Переписать нет возможности — ей 10-15 лет отроду.
CS>А что ты используешь в качестве PDF canvas? Что-то свое или библиотеку какую?
CS>Задача имеет квази-оптимальное решение только если ты сам осуществляешь EMF генерацию и выставляешь большое разрешение — 300 DPI как минимум. CS>Для произвольных же EMF сделанных в масштабе экрана — увы и ах.
идея с сеткой осенила меня — можно же 'двигать' глиф в рамках 'сетки' GDI. Т.е. для глифов у которых разница (w — w') > k, где w — ширина глифа в GDI, w' — ширина глифа из TrueType, k — число, использовать межсимвольное расстояние заданное в EMF (offDx[i] — w). Для остальных глифов — центрировать положение (offDx[i] — w' — (w — w')/2). Параметр k взял равный 3 пикселя. Ниже пример с коррекцией и без.