Расчет позиций глифов в 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
Другие вопросы по тегам