namespace Uniscribe {
[StructLayout(LayoutKind.Sequential)]
public class OptionalBase {}
[StructLayout(LayoutKind.Sequential)]
public class Optional<T> : OptionalBase where T:struct {
public T Value;
}
public static class OptionalExtensionMethods {
public static OptionalBase AsOptional<T>( this T? value ) where T:struct {
return (value != null && value.HasValue)
? new Optional<T>() { Value = value.Value }
: null
;
}
}
public partial class USP {
// http://msdn.microsoft.com/en-us/library/ms776521(VS.85).aspx
[DllImport("usp10.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
static extern void ScriptItemize
( [In] string pwcInChars
, [In] int cInChars
, [In] int cMaxItems
, [In] ScriptControl psControl
, [In] ScriptState psState
, [Out] ScriptItem[] pItems
, out int pcItems
);
public static ScriptItem[] ScriptItemize
( string s
, ScriptControl control
, ScriptState state
)
{
ScriptItem[] items = new ScriptItem[2];
int nitems = 0;
for (int max = 1000 ; max > 0 ; --max ) try {
ScriptItemize( s, s.Length, items.Length, control, state, items, out nitems );
nitems += 1; // nitems doesn't include the terminal item by default
ScriptItem[] result = new ScriptItem[ nitems ];
Array.Copy( items, result, nitems );
return result;
} catch ( OutOfMemoryException ) { // thrown by ScriptItemize if items wasn't large enough
items = new ScriptItem[ items.Length*3/2 ];
continue;
}
throw new ApplicationException
( "Infinite loop detected -- was trying to turn a "
+ items.Length
+ " length string into (at most) "
+ items.Length
+ " items at last attempt."
);
}
// http://msdn.microsoft.com/en-us/library/ms776517(VS.85).aspx
[DllImport("usp10.dll",CharSet=CharSet.Unicode,PreserveSig=false)]
extern static void ScriptShape
( [In] IntPtr hdc
, [In,Out] ScriptCache psc
, [In] string pwcChars
, [In] int cChars
, [In] int cMaxGlyphs
, [In,Out] ScriptAnalysis psa
, [Out] WORD[] pwOutGlyphs
, [Out] WORD[] pwLogClust
, [Out] ScriptVisAttr[] psva
, out int pcGlyphs
);
public static void ScriptShape
( IDeviceContext hdc
, ScriptCache cache
, string s
, ScriptAnalysis psa
, out WORD[] pwOutGlyphs
, out WORD[] pwLogClust
, out ScriptVisAttr[] psva
)
{
int maxglyphs = s.Length*3/2+16;
int nglyphs;
pwLogClust = new WORD[s.Length];
for ( int max = 1000 ; max > 0 ; --max ) {
var glyphs = new WORD[maxglyphs];
var attrs = new ScriptVisAttr[maxglyphs];
try {
ScriptShape( hdc.GetHdc(), cache, s, s.Length, maxglyphs, psa, glyphs, pwLogClust, attrs, out nglyphs );
pwOutGlyphs = new WORD[nglyphs];
psva = new ScriptVisAttr[nglyphs];
Array.Copy( glyphs, pwOutGlyphs, nglyphs );
Array.Copy( attrs, psva, nglyphs );
return;
} catch ( OutOfMemoryException ) {
maxglyphs = maxglyphs*3/2;
continue;
} finally {
hdc.ReleaseHdc();
}
}
throw new ApplicationException
( "Infinite loop detected -- was trying to turn a "
+ s.Length
+ " length string into (at most) "
+ maxglyphs
+ " glyphs at last attempt."
);
}
// http://msdn.microsoft.com/en-us/library/ms776477(VS.85).aspx
[DllImport("usp10.dll",PreserveSig=false)]
static extern void ScriptPlace
( [In] IntPtr hdc
, [In,Out] ScriptCache psc
, [In] WORD[] pwGlyphs
, [In] int cGlyphs
, [In] ScriptVisAttr[] psva
, [In,Out] ScriptAnalysis psa
, [Out] int[] piAdvance
, [Out] GOFFSET[] goffset
, out ABC pABC
);
public static void ScriptPlace
( IDeviceContext hdc
, ScriptCache psc
, WORD[] pwGlyphs
, ScriptVisAttr[] psva
, ScriptAnalysis psa
, out int[] piAdvance
, out GOFFSET[] goffset
, out ABC pABC
)
{
piAdvance = new int[pwGlyphs.Length];
goffset = new GOFFSET[pwGlyphs.Length];
ScriptPlace( hdc.GetHdc(), psc, pwGlyphs, pwGlyphs.Length, psva, psa, piAdvance, goffset, out pABC );
hdc.ReleaseHdc();
}
// http://msdn.microsoft.com/en-us/library/ms776504(VS.85).aspx
[DllImport("usp10.dll", PreserveSig=false)]
static extern void ScriptTextOut
( [In] IntPtr hdc
, [In,Out] ScriptCache psc
, [In] int x
, [In] int y
, [In] StringOutFlags flags
, [In] OptionalBase lprc
, [In] ScriptAnalysis psa
, [In] IntPtr pwcReserved // const WCHAR* -- Reserved; must be a null pointer.
, [In] int iReserved // Reserved; must be 0.
, [In] WORD[] pwGlyphs
, [In] int cGlyphs
, [In] int[] piAdvance
, [In] int[] piJustify
, [In] ref GOFFSET goffset
);
public static void ScriptTextOut
( IDeviceContext hdc
, ScriptCache psc
, int x
, int y
, StringOutFlags flags
, Rect? lprc
, ScriptAnalysis analysis
, WORD[] pwGlyphs
, int[] piAdvance
, int[] piJustify
, GOFFSET goffset
)
{
try {
ScriptTextOut
( hdc.GetHdc()
, psc
, x
, y
, flags
, lprc.AsOptional()
, analysis
, IntPtr.Zero
, 0
, pwGlyphs
, pwGlyphs.Length
, piAdvance
, piJustify
, ref goffset
);
} finally {
hdc.ReleaseHdc();
}
}
}
public partial class GDI {
// http://msdn.microsoft.com/en-us/library/ms533272(VS.85).aspx
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject
( IntPtr hdc
, IntPtr hgdiobj
);
public static void SelectObject
( IDeviceContext hdc
, Font hgdiobj
)
{
var prev = SelectObject( hdc.GetHdc(), hgdiobj.ToHfont() );
hdc.ReleaseHdc();
//return Font.FromHfont(prev); // System.Drawing.Font only supports truetype
}
}
}
And some structures:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Uniscribe.Core;
namespace Uniscribe {
namespace Core {
/// <summary>
/// Equivalent to SCRIPT_STRING_ANALYSIS, which is an opaque typedef (to void*)
/// http://msdn.microsoft.com/en-us/library/ms776471(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)] public struct ScriptStringAnalysis {
IntPtr ptr;
}
/// <summary>
/// Equivalent to a SCRIPT_CACHE*, where SCRIPT_CACHE is an opaque typedef (to void*)
/// http://msdn.microsoft.com/en-us/library/ms776467(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)] public class ScriptCache {
IntPtr ptr;
}
/// <summary>
/// Equivalent to a COLORREF, which is a typedef to a DWORD
/// http://msdn.microsoft.com/en-us/library/ms532655(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)] public struct ColorRef {
// COLORREF is a typedef to DWORD
uint Value; // 0x00BBGGRR
public byte Blue { get { return (byte)((Value>>16) & 0xFF); } set { Value = Value&0xFF00FFFF + value<<16; } }
public byte Green { get { return (byte)((Value>> 8) & 0xFF); } set { Value = Value&0xFFFF00FF + value<< 8; } }
public byte Red { get { return (byte)((Value>> 0) & 0xFF); } set { Value = Value&0xFFFFFF00 + value<< 0; } }
public static ColorRef From( Color c ) { return new ColorRef() { Value = (uint)(c.B << 16 + c.G << 8 + c.R << 0) }; }
public Color ToColor() { return Color.FromArgb( Red, Green, Blue ); }
public static ColorRef Invalid = new ColorRef() { Value = 0xFFFFFFFF }; // == CLR_INVALID
}
/// <summary>
/// Equivalent to a SCRIPT_STATE*
/// http://msdn.microsoft.com/en-us/library/ms776530(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class ScriptState {
public WORD Data;
public int uBidiLevel { get { return (Data.Value & 0x001F) >> 0; } }
public bool fOverrideDirection { get { return (Data.Value & 0x0020) != 0; } }
public bool fInhibitSymSwap { get { return (Data.Value & 0x0040) != 0; } }
public bool fCharShape { get { return (Data.Value & 0x0080) != 0; } }
public bool fDigitSubstitute { get { return (Data.Value & 0x0100) != 0; } }
public bool fInhibitLigate { get { return (Data.Value & 0x0200) != 0; } }
public bool fDisplayZWG { get { return (Data.Value & 0x0400) != 0; } }
public bool fArabicNumContext { get { return (Data.Value & 0x0800) != 0; } }
public bool fGcpClusters { get { return (Data.Value & 0x1000) != 0; } }
// WORD fReserved : 1
// WORD fEngineReserved : 2
}
/// <summary>
/// Equivalent to a SCRIPT_VISATTR
/// http://msdn.microsoft.com/en-us/library/ms776515(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ScriptVisAttr {
WORD data;
public int uJustification { get { return (data.Value & 0x000F) >> 0; } }
public bool fClusterStart { get { return (data.Value & 0x0010) != 0; } }
public bool fDiacritic { get { return (data.Value & 0x0020) != 0; } }
public bool fZeroWidth { get { return (data.Value & 0x0040) != 0; } }
// WORD fReserved : 1
// WORD fShapeReserved : 8
}
/// <summary>
/// Equivalent to a SCRIPT_ANALYSIS*
/// http://msdn.microsoft.com/en-us/library/ms776520(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class ScriptAnalysis {
public WORD Data;
public int eScript { get { return (Data.Value & 0x03FF) >> 0; } }
public bool fRTL { get { return (Data.Value & 0x0400) != 0; } }
public bool fLayoutRTL { get { return (Data.Value & 0x0800) != 0; } }
public bool fLinkBefore { get { return (Data.Value & 0x1000) != 0; } }
public bool fLinkAfter { get { return (Data.Value & 0x2000) != 0; } }
public bool fLogicalOrder { get { return (Data.Value & 0x4000) != 0; } }
public bool fNoGlyphIndex { get { return (Data.Value & 0x8000) != 0; } }
WORD sdata; // corresponds to SCRIPT_STATE member "SCRIPT_STATE s;"
public ScriptState s {
get { return new ScriptState() { Data = sdata }; }
set { sdata = value.Data; }
}
}
/// <summary>
/// Equivalent to a SCRIPT_CONTROL*
/// http://msdn.microsoft.com/en-us/library/ms776493(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class ScriptControl {
DWORD data;
public int uDefaultLanguage { get { return (int)(data.Value & 0x0000FFFF) >> 0; } }
public bool fContextDigits { get { return (data.Value & 0x00010000) != 0; } }
public bool fInvertPreBoundDir { get { return (data.Value & 0x00020000) != 0; } }
public bool fInvertPostBoundDir { get { return (data.Value & 0x00040000) != 0; } }
public bool fLinkStringBefore { get { return (data.Value & 0x00080000) != 0; } }
public bool fLinkStringAfter { get { return (data.Value & 0x00100000) != 0; } }
public bool fNeutralOverride { get { return (data.Value & 0x00200000) != 0; } }
public bool fNumericOverride { get { return (data.Value & 0x00400000) != 0; } }
public bool fLegacyBidiClass { get { return (data.Value & 0x00800000) != 0; } }
public bool fMergeNeutralItems { get { return (data.Value & 0x01000000) != 0; } }
// DWORD fReserved : 7
}
/// <summary>
/// Equivalent to a SCRIPT_ITEM
/// http://msdn.microsoft.com/en-us/library/ms776513(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ScriptItem {
public int iCharPos;
private WORD aData; // corresponds to SCRIPT_ITEM member "SCRIPT_ANALYSIS a;"
public ScriptAnalysis a { get { return new ScriptAnalysis() { Data = aData }; } set { aData = value.Data; } }
}
/// <summary>
/// Stronger typed flags for ScriptStringAnalyse's parameter, DWORD dwFlags.
/// ScriptStringAnalyse's documentation: http://msdn.microsoft.com/en-us/library/ms776516(VS.85).aspx
/// </summary>
public enum StringAnalysisFlags : uint {
None = 0,
Break = 0x0040, //SSA_BREAK Return break flags, that is, character and word stops.
Clip = 0x0004, //SSA_CLIP Clip the string at iReqWidth.
DZWG = 0x0010, //SSA_DZWG Provide representation glyphs for control characters.
Fallback = 0x0020, //SSA_FALLBACK Use fallback fonts.
Fit = 0x0008, //SSA_FIT Justify the string to iReqWidth.
Glyphs = 0x0080, //SSA_GLYPHS Generate glyphs, positions, and attributes.
GCP = 0x0200, //SSA_GCP Return missing glyphs and pwLogClust with GetCharacterPlacement conventions.
HideHotKey = 0x2000, //SSA_HIDEHOTKEY Remove the first '&' from displayed string.
HotKey = 0x0400, //SSA_HOTKEY Replace '&' with underline on subsequent code point.
HotKeyOnly = 0x2400, //SSA_HOTKEYONLY Display underline only. [EDIT: *snip*]
Link = 0x1000, //SSA_LINK Apply East Asian font linking and association to noncomplex text.
Metafile = 0x0800, //SSA_METAFILE Write items with ExtTextOutW calls, not with glyphs.
Password = 0x0001, //SSA_PASSWORD Duplicate input string containing a single character tcString times.
RTL = 0x0100, //SSA_RTL Use base embedding level 1.
Tab = 0x0002, //SSA_TAB Expand tabs.
}
/// <summary>
/// Stronger typed flags for the parameter, UINT uOptions, in various functions:
/// ScriptStringOut's documentation: http://msdn.microsoft.com/en-us/library/ms776499(VS.85).aspx
/// ScriptTextOut's documentation: http://msdn.microsoft.com/en-us/library/ms776504(VS.85).aspx
/// </summary>
public enum StringOutFlags : uint {
None = 0,
Opaque = 0x0002, // ETO_OPAQUE
Clipped = 0x0004, // ETO_CLIPPED
}
/// <summary>
/// Corresponds to RECT
/// http://msdn.microsoft.com/en-us/library/ms536136(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Rect {
public long Left;
public long Top;
public long Right;
public long Bottom;
public long Width { get { return Right-Left; } } // [Left..Right)
public long Height { get { return Bottom-Top; } } // [Top..Bottom)
public override string ToString() {
return Left + "," + Top + "," + Right + "," + Bottom;
}
}
/// <summary>
/// Corresponds to GOFFSET
/// http://msdn.microsoft.com/en-us/library/ms776514(VS.85).aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct GOFFSET {
public long DU;
public long DV;
public long DX { get { return DU; } set { DU = value; } }
public long DY { get { return DV; } set { DV = value; } }
public override string ToString() {
return DU + "," + DV;
}
}
/// <summary>
/// Corresponds to ABC
/// http://msdn.microsoft.com/de-de/library/8e350930.aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ABC {
public int abcA;
public uint abcB;
public int abcC;
public override string ToString() {
return abcA + "," + abcB + "," + abcC;
}
}
[StructLayout(LayoutKind.Sequential)] public struct WORD {
public ushort Value;
public override string ToString() { return Value.ToString(); }
} // WORD is a typedef
[StructLayout(LayoutKind.Sequential)] public struct DWORD {
public uint Value;
public override string ToString() { return Value.ToString(); }
} // DWORD is a typedef
}
}
EDIT: Tried to unbreak the forum width a little bit
And, yes, I'm aware I'm leaking a bit (e.g. SCRIPT_CACHE is supposed to get ScriptCacheFree()d)