Расчет позиций глифов в Windows
Существует ли какая-либо простая и совместимая GDI или.NET доступная подсистема Windows, которая будет давать символы положения глифа. Задачей здесь является объединение символов, таких как символы на арабском языке, которые иногда имеют цепочки из нескольких объединяющих символов, накладывающихся друг на друга, таких как арабская фата + верхний индекс арабской буквы алеф + арабская мадда выше. Проблема в том, что, хотя положения X могут быть точно определены с помощью GDI GetCharacterPlacement, вычисления положения Y, которые получены из таблиц и якорей шрифтов OpenType или TrueType и сложного набора правил, недоступны. В конечном счете, для создания PDF с правильно отформатированным арабским языком, Y-позиции необходимы и точно. Изучая функцию сохранения в формате PDF в Microsoft Word 2013, становится ясно, что у них есть способ правильно собрать эти данные, так как изучение деталей PDF показывает, что каждый символ отображается в своей точной позиции, включая объединяющие символы.
WPF может содержать некоторые функции для этого в свойстве класса GlyphRun GlyphOffsets. DirectWrite имеет интерфейс IDWriteTextAnalyzer, метод которого GetGlyphPlacements может возвращать DWRITE_GLYPH_OFFSETs и много другой сложной информации скрипта. Глядя на функции GDI Display и Printer Drive, STROBJ_bEnumPositionsOnly, похоже, возвращает набор структур GLYPHPOS с этой информацией. GDI, безусловно, отображает это правильно при любых обстоятельствах, если вы отправляете полный текст для рендеринга, но не если вы хотите сделать это глиф за глифом.
IXpsOMGlyphs в объектной модели XPS позволяет вызову GetGlyphIndices, возвращающему набор XPS_GLYPH_INDEX, дает HorizontalOffset и verticalOffset, хотя эта библиотека вряд ли подходит.
В конце концов, единственной подходящей библиотекой является Uniscribe, которая сложна в использовании, но поддерживается начиная с Internet Explorer 5 и Windows 2000, в отличие от всех других обсуждений, помимо GDI, которые обычно относятся к Vista и более поздним версиям или требуют особых зависимостей. ScriptItemize возвращает массив SCRIPT_STRING_ANALYSIS, который может быть передан в ScriptShape, а затем в ScriptPlace, возвращая массив из GOFFSET. На самом деле Uniscribe предоставит информацию о разрывах слов, диакритических знаках, направленном потоке и многих других аспектах того, что происходит в сложном сценарии. Я просто хотел узнать, существует ли более простой метод или это минимально необходимый и точный подход для такой задачи, поскольку Uniscribe представляется чрезвычайно трудным для использования непосредственно из.NET и разумно потребуется оболочка C++, так как есть большой сделка структур и указателей.
Обновление и ответ: Uniscribe не будет работать для целей PDF, поскольку он использует целые числа в единицах устройства GDI, поэтому точность значительно снижается. Вероятно, поэтому Microsoft Word 2013 наконец-то поддерживает встроенную поддержку преобразования PDF, потому что в конечном итоге, похоже, на DirectWrite полагаются. Как упоминалось ниже, я разместил оба решения для кода в.NET в качестве советов по CodeProject. DirectWrite, по-видимому, является единственным ответом помимо разработки собственного механизма формирования и расчета шрифтов.
1 ответ
Пример кода Uniscribe в.NET, поскольку в настоящее время он недоступен в Интернете:
_ Публичная структура GCP_RESULTS Public StructSize как UInteger _ Public OutString как строка Public Public как IntPtr Public Dx как IntPtr Public CaretPos как IntPtr Public [Класс] как IntPtr Public Glyphs как IntPtr Public GlyphCount как UInteger Public MaxFit как структура целочисленных конечных объектов _ Public SCT _ _ SCR Структура ScriptControlFlags в качестве конечной структуры UInteger _ Публичная структура SCRIPT_STATE Публичная структура ScriptStateFlags в качестве конечной структуры US_ _ Публичная структура SCRIPT_ANALYSIS Публичная структура ScriptAnalysisFlags в виде UShort Public в виде конечной структуры SCRIPT_STATE _ Публичная структура SCRIPT_VISATTR Публичная структура SCISTISGST SCRIPT_ANALYSIS Конечная структура _ Открытая структура GOFFSET Public du Как целое число Public dv Как целое число End структура _ Открытая структура ABC Public abcA Как целое число Public abcB Как UInteger Public abcC Как Целое число Конечная структура Public Const E_OUTOFMEMORY As Integer = &H8007000E Public Const E_PENDING в виде целого числа = &H8000000A Публичное константное значение USP_E_SCRIPT_NOT_IN_FONT в виде целого числа = &H80040200 _ Публичная общая функция GetCharacterPlacement(hdc в виде IntPtr, lpString в виде строки, nCount в виде целого числа, nMaxExtent в виде целого числа в качестве целочисленного значения, функция-логика) Функция ScriptItemize( wcInChars как строка, cInChars как целое число, cMaxItems как целое число, psControl как SCRIPT_CONTROL, psState как SCRIPT_STATE, pItems () как SCRIPT_ITEM, ByRef pcItems как целочисленная функция как функция типа Integ-Script В качестве IntPtr, wcChars в виде строки, cChars в виде целого числа, cMaxGlyphs в виде целого числа, ByRef psa в качестве SCRIPT_ANALYSIS, wOutGlyphs() в качестве UShort, wLogClust() в качестве UShort, psva() в качестве SCRIPT_VISATTR в качестве целого параметра Функция ScriptPlace (hdc As IntPtr, ByRef psc As IntPtr, wGlyphs() As UShort, cGlyphs As Integer, psva() As SCRIPT_VISATTR, ByRef psa As SCRIPT_ANALYSIS, iAdvance() как Integer, pGoffset() как GOFFSET, ByRef pABC как ABC) как конечная функция целого числа _ общедоступная общая функция ScriptFreeCache(ByRef psc в виде IntPtr) как конечная функция целого числа _ общедоступная общая функция GetDC(hWnd As IntPtr) как конечная функция IntPtr _ общедоступная общая функция ReleaseDC(HWND Как IntPtr, HDC As IntPtr) As Integer End Function _ Private Shared Function SelectObject(ByVal HDC Как IntPtr, ByVal hObject Как IntPtr) As IntPtr End Function Структура CharPosInfo Public Index As Integer Public Ширина As Integer Public PriorWidth As Integer Public X Как целое число Public Y Как целое число Конечная структура Открытая общая функция GetWordDiacriticPositions(Str As String, useFont As Font) Как CharPosInfo() Dim hdc As IntPtr Dim CharPosInfos как новый список (из CharPosInfo) hdc = GetDC(IntPtr.Zero) Dim oldFont As IntPtr = SelectObject(hdc, useFont.ToHfont()) Dim MaxItems As Integer = 16 Dim Control As New SCRIPT_CONTROL с {.ScriptControlFlags = 0} затемненным состоянием как новым SCRIPT_STATE с {.ScriptStateFlags = 1} '0 LTR, 1 RTL Dim Items() As SCRIPT_ITEM = Nothing Dim ItemCount As Integer Dim Результат как Integer Делать объекты Reimim (MaxItems - 1) Result = ScriptItemize(Str, Str.Length, MaxItems, Control, State, Items, ItemCount) Если Result = 0, то ReDim Preserve Items(ItemCount) "есть фиктивный последний элемент, поэтому добавьте его здесь Exit Do ElseIf Result = E_OUTOFMEMORY Then End End MaxItems *= 2 Loop While True Если Result = 0 Then 'последний элемент является фиктивным элементом, указывающим до конца строки Dim Cache As IntPtr = IntPtr.Zero For Count = 0 To ItemCount - 2 Dim Logs() As UShort = Nothing Dim Glyphs() As UShort = Nothing Dim VisAttrs() As SCRIPT_VISATTR = Nothing ReDim Glyphs((Items(Количество + 1).iCharPos - Предметы (Количество).iCharPos) * 3 \ 2 + 16 - 1) ReDim VisAttrs((Предметы (Количество + 1).iCharPos - Элементы (Количество).iCharPos) * 3 \ 2 + 16 - 1) ReDim Logs(Items(Count + 1).iCharPos - Items(Count).iCharPos - 1) Dim dc As IntPtr = IntPtr.Zero Do Dim GlyphsUsed в качестве целочисленного результата = ScriptShape(dc, Cache, Str.Substring(Items(Count).iCharPos), Предметы (Count + 1).iCharPos - Items(Count).iCharPos, Glyphs.Length, Items(Count).a, Глифы, Журналы, VisAttrs, GlyphsUsed) Если Result = 0, то ReDim Сохраняет глифы (GlyphsUsed - 1) ReDim Сохраняет VisAttrs(GlyphsUsed - 1) Exit Do ElseIf Result = E_PENDING Тогда dc = hdc ElseIf Result = E_OUTOFMEMORY Затем ReDim Glyphs(Glyphs.Length * 2 - 1) ReDim VisAttrs(VisAttrs.Length * 2 - 1) Else_FT__S_T_NT_T_F_T_N_T_F_T_N_T_F_T_N_T_F_T_N_T_N_T_S_T_N_T_F_RU_ID_E_S_INF Цикл пока истина, если результат = 0, то затемнение продвигается (Glyphs.Length - 1) как смещение целочисленного затемнения (Glyphs.Length - 1) как GOFFSET Dim abc как новое ABC с {.abcA = 0, .abcB = 0, .abcC = 0} dc = IntPtr.Zero Do Result = ScriptPlace(dc, Cache, Glyphs, Glyphs.Length, VisAttrs, Items(Count).a, Advances, Offsets, abc) Если результат E_PENDING, то выход Do Do dc = цикл hdc, в то время как True если Результат = 0, затем Dim LastPriorWidth как целое число = 0 Dim RunStart как целое число = 0 для CharCount = 0 для Logs.Length - 1 Dim PriorWidth как целое число = 0 Dim RunCount как целое число = 0 для ResCount как целое число = журналы (CharCount) To If(CharCount = Logs.Length - 1, 0, Logs(CharCount + 1)) Шаг -1 'fDiacritic или fZeroWidth If (VisAttrs(ResCount).ScriptVisAttrFlags And (32 или 64)) 0 Тогда CharPosInfos.Add(Новое CharPosInfo с {.Index = RunStart + RunCount, .PriorWidth = LastPriorWidth, .Width = Advances(ResCount), .X = Offsets(ResCount).du, .Y = Offsets(ResCount).dv}) End If If CharCount = Logs.Length - 1 Журналы OrElse (CharCount) Журналы (CharCount + 1) Затем PriorWidth += Advances(ResCount) RunCount += 1 Конец, если следующий LastPriorWidth += PriorWidth If CharCount = Logs.Length - 1 Журналы OrElse (CharCount) (CharCount + 1) Тогда RunStart = CharCount + 1 Конец, если следующий конец, Конец, если конец, ScriptFreeCache(Cache) Конец, если SelectObject(hdc, oldFont) ReleaseDC(IntPtr.Zero, hdc) Возвращает CharPosInfos.ToArray() End Function