Sign in to follow this  
MaulingMonkey

Uniscribe character placement issues

Recommended Posts

I'm at a loss as to what I'm doing wrong.
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Uniscribe;
using Uniscribe.Core;

namespace HelloWorld {
	public partial class Form1 : Form {
		private string s = "o";

		public Form1() {
			var t = new Timer();
			t.Tick += new System.EventHandler(t_Tick);
			t.Interval = 500;
			t.Enabled = true;
			InitializeComponent();
		}

		void t_Tick(object sender, System.EventArgs e) {
			s += "o";
			Invalidate();
		}

		private void Form1_Paint(object sender, PaintEventArgs e) {
			// http://www.catch22.net/tuts/neatpad/11

			//string s = "Hello, world!\u064A\u064F\u0633\u0627\u0648\u0650\u064A How are you?";
			//string s = "ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo";

			GDI.SelectObject( e.Graphics, new Font( "Times New Roman", 20.0f ) );
			ScriptStringAnalysis ssa = USP.ScriptStringAnalyse
				( e.Graphics
				, s
				, StringAnalysisFlags.Glyphs | StringAnalysisFlags.Fallback
				, 500
				, null
				, null
				, null
				, null
				, null
				);
			Size size = USP.ScriptStringSize(ssa);
			e.Graphics.DrawRectangle( Pens.Red, new Rectangle( 9, 9, size.Width+1, size.Height+1 ) );
			USP.ScriptStringOut( ssa, 10, 10, StringOutFlags.None, null, 0, 0, false );
			USP.ScriptStringFree( ref ssa );

			// http://www.catch22.net/tuts/neatpad/12

			var control    = new ScriptControl();
			var state      = new ScriptState();
			var items      = USP.ScriptItemize( s, control, state );
			var bidilevels = ( from item in items select (byte)item.a.s.uBidiLevel ).ToArray<byte>();
			//var vtl        = new int[items.Length];
			//USP.ScriptLayout( bidilevels, vtl, null );
			WORD[] glyphs;
			WORD[] logclust;
			ScriptVisAttr[] attrs;
			var analysis   = new ScriptAnalysis();
			var cache      = new ScriptCache();
			USP.ScriptShape( e.Graphics, cache, s, analysis, out glyphs, out logclust, out attrs );
			// TODO:  Font Fallback?
			int[] advance;
			GOFFSET[] goffsets;
			ABC abc;
			USP.ScriptPlace( e.Graphics, cache, glyphs, attrs, analysis, out advance, out goffsets, out abc );
			USP.ScriptTextOut( e.Graphics, cache, 10, 110, StringOutFlags.None, null, analysis, glyphs, advance, null, new GOFFSET() );
		}
	}
}





A simple pair of tests of Uniscribe -- the top section using the higher level ScriptString* series of functions and rendering correctly, the bottom selection using the more versatile basic functions exploding horribly: I have no idea what I'm doing wrong. Unfortunately, there's a large bulk of code that could be suspect. All the arrays seem to be the right size, goffsets is full of (0,0)s, advances is also full of 0s, glyphs is all a single value (the o glyph), logclust increments normally (logclust[i] = i since everything is LTR and we have a 1:1 character to glyph mapping), and we have one "items". I'm running out of ideas as to what could be wrong. I've double checked that each Uniscribe.Core.* class/structure/function matches it's usp10.h equivalent. Some relevant functions:
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)

Share this post


Link to post
Share on other sites
It's a sad day when I manage to write a C++ equivalent to what I'm trying to do in C# and it works on the first try:

#include <windows.h>
#include <usp10.h>
#include <vector>

namespace uniscribe {
extern "C" __declspec(dllexport) HRESULT USPPTextOut( HDC hdc, int x, int y, HFONT font, COLORREF color, const wchar_t* string, int strlen ) {
HRESULT error = S_OK;



SelectObject( hdc, font );
SetTextAlign( hdc, TA_LEFT | TA_TOP );
//SetTextColor( hdc, color ); // doesn't actually set the color for uniscribe



std::vector< SCRIPT_ITEM > items(2);
SCRIPT_CONTROL control = {0};
SCRIPT_STATE state = {0};
for ( int nitems ;; items.resize( items.size() * 2 ) ) {
error = ScriptItemize( string, strlen, items.size(), &control, &state, &items[0], &nitems );
if ( error == E_OUTOFMEMORY ) continue;
if ( error != S_OK ) return error;
items.resize( nitems+1 ); // include terminal item
break;
}



std::vector< WORD > glyphs( strlen*3/2+16 );
std::vector< WORD > logclust( strlen );
std::vector< SCRIPT_VISATTR > visattr( glyphs.size() );
SCRIPT_CACHE cache = {0};
SCRIPT_ANALYSIS analysis = {0};
for ( int nglyphs ;; glyphs.resize( glyphs.size() * 2 ), visattr.resize( glyphs.size() ) ) {
error = ScriptShape( hdc, &cache, string, strlen, glyphs.size(), &analysis, &glyphs[0], &logclust[0], &visattr[0], &nglyphs );
if ( error == E_OUTOFMEMORY ) continue;
if ( error != S_OK ) return error;
glyphs.resize( nglyphs );
visattr.resize( nglyphs );
break;
}



std::vector< GOFFSET > goffsets(glyphs.size());
std::vector< int > advance(glyphs.size());
ABC abc;
error = ScriptPlace( hdc, &cache, &glyphs[0], glyphs.size(), &visattr[0], &analysis, &advance[0], &goffsets[0], &abc );
if ( error != S_OK ) return error;



error = ScriptTextOut( hdc, &cache, x, y, ETO_OPAQUE, NULL, &analysis, NULL, 0, &glyphs[0], glyphs.size(), &advance[0], NULL, &goffsets[0] );
if ( error != S_OK ) return error;



return S_OK;

//var bidilevels = ( from item in items select (byte)item.a.s.uBidiLevel ).ToArray<byte>();
//var vtl = new int[items.Length];
//USP.ScriptLayout( bidilevels, vtl, null );
}
}





At this point it looks like I'll be switching over to C++/CLI, after MikePopoloski poked me to give him the error messages that trying to #include <usp10.h> in a C++/CLI project, and I couldn't because it compiled fine.

Still, it'd be nice to know what I was doing wrong. Because I must be doing *something* wrong.

[Edited by - MaulingMonkey on January 18, 2009 3:53:26 PM]

Share this post


Link to post
Share on other sites


Of course, my code is a horribly fugly run on function...

// Uniscribe++.h

#pragma once
#using <System.Drawing.dll>

using namespace System;
using namespace System::Collections::Generic;
using namespace System::Drawing;
using namespace System::Runtime::InteropServices;

namespace Uniscribe {
public value class TextRun
{
public:
String^ Text;
Font^ Font;
Color^ Foreground;
Color^ Background;
};

private class ParagraphDataCache {
public:
//SCRIPT_ANALYSIS analysis;
//SCRIPT_CACHE cache;
SCRIPT_CONTROL control;
SCRIPT_STATE state;
};

public ref class Paragraph
{
ParagraphDataCache* data;
List<TextRun>^ runs;

Font^ Font;
Color^ Foreground;
Color^ Background;
public:
Paragraph(): data(nullptr), runs(gcnew List<TextRun>()) {}
~Paragraph() { delete data; data = nullptr; runs = nullptr; }
!Paragraph() { delete data; }

void Add( TextRun run ) {
delete data;
data = nullptr;
runs->Add( run );
}

void DrawTo( Graphics^ fx, int x, int y ) {
if (!data) data = new ParagraphDataCache();

std::wstring merged_text;
//std::vector< int > run_to_merged_pos;

const wchar_t* text = nullptr;
for each ( TextRun run in runs ) {
try {
//run_to_merged_pos.push_back(merged_text.size());
text = (const wchar_t*)Marshal::StringToHGlobalUni(run.Text).ToPointer();
int textlen = run.Text->Length;
merged_text.insert( merged_text.end(), text+0, text+textlen );
} finally {
Marshal::FreeHGlobal( (IntPtr) const_cast<wchar_t*>(text) );
text = nullptr;
}
}

// Itemize
std::vector<SCRIPT_ITEM> items(2);
for ( int nitems ;; items.resize(items.size()+1), items.resize(items.capacity()) ) {
HRESULT error = ScriptItemize
( merged_text.c_str(), merged_text.size(), items.size()
, &data->control, &data->state, &items[0], &nitems
);
if ( error == E_OUTOFMEMORY ) continue;
if ( error != S_OK ) throw Marshal::GetExceptionForHR(error);
items.resize(nitems+1); // +1 to include terminal case which nitems omits
break;
}

// Split SCRIPT_ITEMs based on font
System::Drawing::Font^ prev_font = nullptr;
int iCharPos = 0;
for each ( TextRun run in runs ) {
if ( run.Font == nullptr ) run.Font = Font;
if ( run.Font != prev_font ) {
// items must be split at iCharPos
// TODO: Replace O(N*N) algorithm with O(N)
std::vector<SCRIPT_ITEM>::iterator i = std::find_if
( items.begin(), items.end()
, boost::bind( &SCRIPT_ITEM::iCharPos , _1 ) > iCharPos
);

assert( i != items.end() );
SCRIPT_ITEM clone = i[-1];
clone.iCharPos = iCharPos;
items.insert( i, clone );
}
iCharPos += run.Text->Length;
}

// Map ITEM_RUNs to fonts
List<System::Drawing::Font^> fonts;
std::vector<SCRIPT_ITEM>::iterator item = items.begin();
iCharPos = 0;
TextRun previous = runs[0];
for each ( TextRun run in runs ) {
for ( ; item != items.end() && item->iCharPos < iCharPos ; ++item ) fonts.Add( previous.Font );
previous = run;
iCharPos += run.Text->Length;
}
while ( item != items.end() ) fonts.Add( previous.Font ), ++item;



// Script Layout
std::vector<BYTE> bidi;
using boost::bind;
for each ( const SCRIPT_ITEM& item in items ) bidi.push_back( item.a.s.uBidiLevel );
std::vector<int> vtol(bidi.size());
HRESULT error = ScriptLayout( bidi.size(), &bidi[0], &vtol[0], nullptr );



HDC hdc = (HDC)fx->GetHdc().ToPointer();
try {
for ( unsigned i = 0 ; i < items.size()-1 ; ++i ) {
SCRIPT_ITEM& item = items[vtol[i]];
const SCRIPT_ITEM& next = items[vtol[i]+1];
int len = next.iCharPos-item.iCharPos;
if ( len == 0 ) continue;
System::Drawing::Font^% font = fonts[i];

SelectObject( hdc, (HFONT)font->ToHfont().ToPointer() );

// Shape items
std::vector< WORD > glyphs( item.iCharPos*3/2+16 );
std::vector< WORD > logclust( len );
std::vector< SCRIPT_VISATTR > visattr( glyphs.size() );
SCRIPT_CACHE cache = {0};
for ( int nglyphs ;; glyphs.resize( glyphs.size() * 2 ), visattr.resize( glyphs.size() ) ) {
HRESULT error = ScriptShape
( hdc, &cache, &merged_text[item.iCharPos], len, glyphs.size(),
&item.a, &glyphs[0], &logclust[0], &visattr[0], &nglyphs
);
if ( error == E_OUTOFMEMORY ) continue;
if ( error != S_OK ) throw Marshal::GetExceptionForHR(error);
glyphs.resize( nglyphs );
visattr.resize( nglyphs );
break;
}

// Place Items
std::vector< GOFFSET > goffsets(glyphs.size());
std::vector< int > advance(glyphs.size());
ABC abc;
HRESULT error = ScriptPlace
( hdc, &cache, &glyphs[0], glyphs.size(), &visattr[0]
, &item.a, &advance[0], &goffsets[0], &abc
);
if ( error != S_OK ) throw Marshal::GetExceptionForHR(error);

// Text Out
error = ScriptTextOut
( hdc, &cache, x, y, ETO_OPAQUE, NULL, &item.a, NULL, 0
, &glyphs[0], glyphs.size(), &advance[0], NULL, &goffsets[0]
);
x += abc.abcA + abc.abcB + abc.abcC;
if ( error != S_OK ) throw Marshal::GetExceptionForHR(error);
}
} finally {
fx->ReleaseHdc();
}
}
};
}







using System;
using System.Drawing;
using System.Windows.Forms;
using Uniscribe;

namespace HelloWorld {
public partial class Form1 : Form {
Paragraph p;

public Form1() {
p = new Paragraph();
p.Add( new TextRun()
{ Foreground = Color.Black
, Background = Color.White
, Font = new Font( "Times New Roman", 10.0f )
, Text = "Hello, world!\u064A\u064F\u0633\u0627\u0648\u0650\u064A How are you?"
});
p.Add( new TextRun()
{ Foreground = Color.Black
, Background = Color.White
, Font = new Font( "Times New Roman", 20.0f )
, Text = "Hello, world!\u064A\u064F\u0633\u0627\u0648\u0650\u064A How are you?"
});

InitializeComponent();
}

void Form1_Paint( object sender, PaintEventArgs args ) {
p.DrawTo( args.Graphics, 10, 10 );
}
}
}




Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this