ExportFont.Uniscribe.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. #if !WITHOUT_UNISCRIBE
  2. using FastReport.Fonts;
  3. using FastReport.Utils;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.Drawing;
  9. using System.Drawing.Imaging;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Runtime.InteropServices;
  13. namespace FastReport.Export.TTF
  14. {
  15. internal partial class ExportTTFFont
  16. {
  17. #region DLL import
  18. [DllImport("Gdi32.dll")]
  19. private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
  20. [DllImport("Gdi32.dll")]
  21. private static extern IntPtr DeleteObject(IntPtr hgdiobj);
  22. [DllImport("Gdi32.dll")]
  23. private static extern int GetOutlineTextMetrics(IntPtr hdc, int cbData, ref TrueTypeFont.OutlineTextMetric lpOTM);
  24. [DllImport("Gdi32.dll", CharSet = CharSet.Auto)]
  25. private static extern uint GetFontData(IntPtr hdc, uint dwTable, uint dwOffset, [In, Out] IntPtr lpvBuffer, uint cbData);
  26. [DllImport("Gdi32.dll")]
  27. private static extern int GetCharWidthI(IntPtr hdc, int giFirst, int cgi, [MarshalAs(UnmanagedType.LPArray)] ushort[] pgi, [Out, MarshalAs(UnmanagedType.LPArray)] int[] piWidths);
  28. [DllImport("usp10.dll")]
  29. private static extern int ScriptFreeCache(ref IntPtr psc);
  30. [DllImport("usp10.dll")]
  31. private static extern int ScriptIsComplex(
  32. [MarshalAs(UnmanagedType.LPWStr)] string pwcInChars,
  33. int cInChars, int dwFlags);
  34. [DllImport("usp10.dll")]
  35. private static extern int ScriptItemize(
  36. [MarshalAs(UnmanagedType.LPWStr)] string pwcInChars, int cInChars, int cMaxItems,
  37. ref SCRIPT_CONTROL psControl, ref SCRIPT_STATE psState, [In, Out] SCRIPT_ITEM[] pItems, ref int pcItems);
  38. [DllImport("usp10.dll")]
  39. private static extern int ScriptLayout(
  40. int cRuns, [MarshalAs(UnmanagedType.LPArray)] byte[] pbLevel,
  41. [MarshalAs(UnmanagedType.LPArray)] int[] piVisualToLogical,
  42. [MarshalAs(UnmanagedType.LPArray)] int[] piLogicalToVisual);
  43. [DllImport("usp10.dll")]
  44. private static extern int ScriptShape(
  45. IntPtr hdc, ref IntPtr psc, [MarshalAs(UnmanagedType.LPWStr)] string pwcChars,
  46. int cChars, int cMaxGlyphs, ref SCRIPT_ANALYSIS psa,
  47. [Out, MarshalAs(UnmanagedType.LPArray)] ushort[] pwOutGlyphs,
  48. [Out, MarshalAs(UnmanagedType.LPArray)] ushort[] pwLogClust,
  49. [Out, MarshalAs(UnmanagedType.LPArray)] SCRIPT_VISATTR[] psva, ref int pcGlyphs);
  50. [DllImport("usp10.dll")]
  51. private static extern int ScriptPlace(
  52. IntPtr hdc, ref IntPtr psc, [MarshalAs(UnmanagedType.LPArray)] ushort[] pwGlyphs,
  53. int cGlyphs, [MarshalAs(UnmanagedType.LPArray)] SCRIPT_VISATTR[] psva,
  54. ref SCRIPT_ANALYSIS psa, [MarshalAs(UnmanagedType.LPArray)] int[] piAdvance,
  55. [Out, MarshalAs(UnmanagedType.LPArray)] GOFFSET[] pGoffset, ref ABC pABC);
  56. [DllImport("usp10.dll")]
  57. private static extern uint ScriptRecordDigitSubstitution(uint lcid, ref SCRIPT_DIGITSUBSTITUTE psds);
  58. [DllImport("usp10.dll")]
  59. private static extern int ScriptApplyDigitSubstitution(
  60. ref SCRIPT_DIGITSUBSTITUTE psds, ref SCRIPT_CONTROL psc, ref SCRIPT_STATE pss);
  61. #endregion
  62. #region Uniscribe Structures
  63. [StructLayout(LayoutKind.Sequential)]
  64. public struct SCRIPT_STATE
  65. {
  66. public short data;
  67. public int uBidiLevel
  68. {
  69. get { return data & 0x001F; }
  70. }
  71. public void SetRtl()
  72. {
  73. data = 0x801;
  74. }
  75. }
  76. [StructLayout(LayoutKind.Sequential)]
  77. public struct SCRIPT_ANALYSIS
  78. {
  79. public short data;
  80. public SCRIPT_STATE state;
  81. }
  82. [StructLayout(LayoutKind.Sequential)]
  83. public struct SCRIPT_CONTROL
  84. {
  85. public int data;
  86. }
  87. [StructLayout(LayoutKind.Sequential)]
  88. public struct SCRIPT_DIGITSUBSTITUTE
  89. {
  90. public short NationalDigitLanguage;
  91. public short TraditionalDigitLanguage;
  92. public byte DigitSubstitute;
  93. public int dwReserved;
  94. }
  95. [StructLayout(LayoutKind.Sequential)]
  96. public struct SCRIPT_ITEM
  97. {
  98. public int iCharPos;
  99. public SCRIPT_ANALYSIS analysis;
  100. }
  101. [StructLayout(LayoutKind.Sequential)]
  102. public struct SCRIPT_VISATTR
  103. {
  104. // WORD uJustification : 4;
  105. // WORD fClusterStart : 1;
  106. // WORD fDiacritic : 1;
  107. // WORD fZeroWidth : 1;
  108. // WORD fReserved : 1;
  109. // WORD fShapeReserved : 8;
  110. public ushort data;
  111. public ushort uJustification
  112. {
  113. get { return (ushort)(data & 15u); }
  114. }
  115. public bool fClusterStart
  116. {
  117. get { return (data & 16u) == 16u; }
  118. }
  119. public bool fDiacritic
  120. {
  121. get { return (data & 32u) == 32u; }
  122. }
  123. public bool fZeroWidth
  124. {
  125. get { return (data & 64u) == 64u; }
  126. }
  127. public bool fReserved
  128. {
  129. get { return (data & 128u) == 128u; }
  130. }
  131. public byte fShapeReserved
  132. {
  133. get { return (byte)((data & 65280u) / 256); }
  134. }
  135. }
  136. [StructLayout(LayoutKind.Sequential)]
  137. public struct GOFFSET
  138. {
  139. public int du;
  140. public int dv;
  141. }
  142. [StructLayout(LayoutKind.Sequential)]
  143. public struct ABC
  144. {
  145. public int abcA;
  146. public int abcB;
  147. public int abcC;
  148. }
  149. #endregion
  150. #region EMF structures
  151. public const int LF_FACESIZE = 32;
  152. public const int LF_FULLFACESIZE = 64;
  153. public const int ELF_VENDOR_SIZE = 4;
  154. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  155. public struct EMREXTCREATEFONTINDIRECT
  156. {
  157. public int ihFont; // Font handle index
  158. public EXTLOGFONT elfw;
  159. }
  160. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  161. public struct EXTLOGFONT
  162. {
  163. public LOGFONT elfLogFont;
  164. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FULLFACESIZE)]
  165. public string elfFullName;
  166. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
  167. public string elfStyle;
  168. public int elfVersion; // 0 for the first release of NT
  169. public int elfStyleSize;
  170. public int elfMatch;
  171. public int elfReserved;
  172. [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = ELF_VENDOR_SIZE)]
  173. public byte[] elfVendorId;
  174. public int elfCulture; // 0 for Latin
  175. public PANOSE elfPanose;
  176. }
  177. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Size = 92)]
  178. public struct LOGFONT
  179. {
  180. public int lfHeight;
  181. public int lfWidth;
  182. public int lfEscapement;
  183. public int lfOrientation;
  184. public int lfWeight;
  185. public byte lfItalic;
  186. public byte lfUnderline;
  187. public byte lfStrikeOut;
  188. public byte lfCharSet;
  189. public byte lfOutPrecision;
  190. public byte lfClipPrecision;
  191. public byte lfQuality;
  192. public byte lfPitchAndFamily;
  193. [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
  194. public string lfFaceName;
  195. }
  196. [StructLayout(LayoutKind.Sequential)]
  197. public struct PANOSE
  198. {
  199. public byte bFamilyType;
  200. public byte bSerifStyle;
  201. public byte bWeight;
  202. public byte bProportion;
  203. public byte bContrast;
  204. public byte bStrokeVariation;
  205. public byte bArmStyle;
  206. public byte bLetterform;
  207. public byte bMidline;
  208. public byte bXHeight;
  209. }
  210. #endregion
  211. #region Private variables
  212. private Cache cache = new Cache();
  213. private SCRIPT_DIGITSUBSTITUTE digitSubstitute;
  214. #endregion
  215. #region Private methods
  216. public void FillOutlineTextMetrix()
  217. {
  218. if (sourceFont != null)
  219. {
  220. using (Graphics g = Graphics.FromImage(tempBitmap))
  221. {
  222. IntPtr hdc = g.GetHdc();
  223. IntPtr f = sourceFont.ToHfont();
  224. try
  225. {
  226. SelectObject(hdc, f);
  227. GetOutlineTextMetrics(hdc, Marshal.SizeOf(typeof(TrueTypeFont.OutlineTextMetric)), ref textMetric);
  228. }
  229. finally
  230. {
  231. DeleteObject(f);
  232. g.ReleaseHdc(hdc);
  233. }
  234. }
  235. }
  236. }
  237. internal static void GDI32_GetFontData(Font font, out IntPtr font_data, out FontType CollectionMode, out uint fontDataSize)
  238. {
  239. font_data = IntPtr.Zero;
  240. Bitmap tempBitmap = new Bitmap(1, 1);
  241. using (Graphics g = Graphics.FromImage(tempBitmap))
  242. {
  243. IntPtr hdc = g.GetHdc();
  244. IntPtr f = font.ToHfont();
  245. SelectObject(hdc, f);
  246. try
  247. {
  248. // Try to read TrueTypeCollection
  249. CollectionMode = FontType.TrueTypeCollection;
  250. fontDataSize = GetFontData(hdc, (uint)CollectionMode, 0, IntPtr.Zero, 0);
  251. if (fontDataSize == uint.MaxValue)
  252. {
  253. CollectionMode = FontType.TrueTypeFont;
  254. fontDataSize = GetFontData(hdc, (uint)CollectionMode, 0, IntPtr.Zero, 0);
  255. }
  256. font_data = Marshal.AllocHGlobal((int)fontDataSize);
  257. GetFontData(hdc, (uint)CollectionMode, 0, font_data, fontDataSize);
  258. }
  259. finally
  260. {
  261. DeleteObject(f);
  262. g.ReleaseHdc(hdc);
  263. tempBitmap.Dispose();
  264. }
  265. }
  266. }
  267. internal static FontStream GetFontStream(Font source_font)
  268. {
  269. MemoryStream mem_stream;
  270. IntPtr font_image;
  271. FontType mode;
  272. uint size;
  273. GDI32_GetFontData(source_font, out font_image, out mode, out size);
  274. byte[] buffer = new byte[size];
  275. Marshal.Copy(font_image, buffer, 0, buffer.Length);
  276. mem_stream = new MemoryStream(buffer);
  277. buffer = null;
  278. return new FontStream(mem_stream);
  279. }
  280. private Font[] GetFallbackFonts(IntPtr hdc, Font originalFont, string text)
  281. {
  282. // sad but true. To obtain a fallback font, we draw a string on a metafile, then analyse its records.
  283. // https://chromium.googlesource.com/chromium/src/+/22aed04422b04b2cf04f7b7d61392da4e9a2c85a/ui/gfx/font_fallback_win.cc
  284. Metafile mf = new Metafile(hdc, EmfType.EmfOnly);
  285. using (Graphics g = Graphics.FromImage(mf))
  286. {
  287. g.DrawString(text, originalFont, Brushes.Black, 0, 0);
  288. }
  289. List<Font> fallbackFonts = new List<Font>();
  290. using (Graphics g = Graphics.FromHdc(hdc))
  291. {
  292. g.EnumerateMetafile(mf, Point.Empty, (EmfPlusRecordType recordType, int flags, int dataSize, IntPtr data, PlayRecordCallback callbackData) =>
  293. {
  294. if (recordType == EmfPlusRecordType.EmfExtCreateFontIndirect)
  295. {
  296. EMREXTCREATEFONTINDIRECT rec = (EMREXTCREATEFONTINDIRECT)Marshal.PtrToStructure(data, typeof(EMREXTCREATEFONTINDIRECT));
  297. fallbackFonts.Add(Font.FromLogFont(rec.elfw.elfLogFont));
  298. }
  299. return true;
  300. });
  301. }
  302. return fallbackFonts.ToArray();
  303. }
  304. private RunInfo GetRunInfo(IntPtr hdc, Run run, Font originalFont, Font measureFont)
  305. {
  306. // initialize structures
  307. int maxGlyphs = run.text.Length * 3;
  308. SCRIPT_ANALYSIS psa = run.analysis;
  309. ushort[] clusters = new ushort[maxGlyphs];
  310. SCRIPT_VISATTR[] psva = new SCRIPT_VISATTR[maxGlyphs];
  311. GOFFSET[] gOffset = new GOFFSET[maxGlyphs];
  312. ABC abc = new ABC();
  313. ushort[] glyphs = new ushort[maxGlyphs];
  314. int[] widths = new int[maxGlyphs];
  315. int numGlyphs = 0;
  316. int fallbackIndex = -1;
  317. bool hasEmptyGlyphs = false;
  318. Font font = originalFont;
  319. Font[] fallbackFonts = null;
  320. var cacheItem = cache.GetCacheItem(hdc, measureFont);
  321. while (true)
  322. {
  323. // make glyphs
  324. ScriptShape(hdc, ref cacheItem.usCache, run.text, run.text.Length, glyphs.Length, ref psa, glyphs, clusters, psva, ref numGlyphs);
  325. // check missing glyphs
  326. hasEmptyGlyphs = false;
  327. if (numGlyphs == 0)
  328. hasEmptyGlyphs = true;
  329. else
  330. {
  331. for (int i = 0; i < numGlyphs; i++)
  332. {
  333. if (glyphs[i] == 0)
  334. {
  335. hasEmptyGlyphs = true;
  336. break;
  337. }
  338. }
  339. }
  340. if (!hasEmptyGlyphs)
  341. break;
  342. else
  343. {
  344. // first call - generate fallback fonts list
  345. if (fallbackIndex == -1)
  346. {
  347. // obtain fallback fonts. Use sourceFont instead of originalFont as a source (because of round errors in case of small fonts).
  348. // sourceFont is 750pt size.
  349. fallbackFonts = GetFallbackFonts(hdc, sourceFont, run.text);
  350. }
  351. fallbackIndex++;
  352. if (fallbackIndex >= fallbackFonts.Length)
  353. break;
  354. Font fallbackFont = fallbackFonts[fallbackIndex];
  355. // fallback font size may not be the same as original (case: japanese fonts)
  356. float fallbackSizeRatio = fallbackFont.SizeInPoints / sourceFont.SizeInPoints;
  357. // measureFont must be the same size as sourceFont (750pt)
  358. measureFont = new Font(fallbackFont.FontFamily, sourceFont.Size, sourceFont.Style);
  359. // finally, the font that will be used in runInfo
  360. font = new Font(fallbackFont.FontFamily, originalFont.Size * fallbackSizeRatio, originalFont.Style);
  361. cacheItem = cache.GetCacheItem(hdc, measureFont);
  362. }
  363. }
  364. // make widths
  365. ScriptPlace(hdc, ref cacheItem.usCache, glyphs, numGlyphs, psva, ref psa, widths, gOffset, ref abc);
  366. RunInfo runInfo = new RunInfo(numGlyphs)
  367. {
  368. Font = font
  369. };
  370. if (numGlyphs > 0)
  371. {
  372. // make ToUnicode
  373. for (int i = 0; i < run.text.Length; i++)
  374. {
  375. int glyphIndex = clusters[i];
  376. if (String.IsNullOrEmpty(runInfo.GlyphToUnicode[glyphIndex]))
  377. runInfo.GlyphToUnicode[glyphIndex] = "";
  378. runInfo.GlyphToUnicode[glyphIndex] += run.text[i];
  379. }
  380. }
  381. // get glyph widths (non-shaped) and use it instead advance widths provided by ScriptPlace
  382. // The reason is that gdi+ does not use kerning. Widths returned by ScriptPlace may vary,
  383. // we need constant glyph width to set up pdf Widths array
  384. int[] gwidths = font == originalFont ?
  385. GetGlyphWidths(hdc, numGlyphs, glyphs) : // no font fallback, use faster method
  386. GetGlyphWidthsSlow(hdc, numGlyphs, glyphs);
  387. for (int i = 0; i < numGlyphs; i++)
  388. {
  389. runInfo.Glyphs[i] = glyphs[i];
  390. runInfo.Widths[i] = gwidths[i];
  391. // we may get kerning like this, but it will result in wrong alignment of right- or center-aligned text because gdi+ draws/measures text w/o kerning
  392. //runInfo.Kernings[i] = widths[i] - gwidths[i];
  393. // the only kerning we use is one for complex script's special symbols
  394. // works but requires width correction
  395. /*runInfo.Kernings[i] = -gOffset[i].du + widths[i] - gwidths[i];
  396. if (i > 0)
  397. {
  398. runInfo.Kernings[i - 1] += gOffset[i].du;
  399. }
  400. else
  401. {
  402. runInfo.ZeroOffsetX = gOffset[i].du * font.Size / sourceFont.Size;
  403. }
  404. // this should be converted to objfont units
  405. runInfo.VerticalOffsets[i] = -gOffset[i].dv * font.Size / sourceFont.Size;*/
  406. // works
  407. if (gOffset[i].du != 0 || gOffset[i].dv != 0)
  408. {
  409. if (i > 0)
  410. {
  411. if (gOffset[i - 1].du != 0)
  412. runInfo.Kernings[i - 1] += gOffset[i].du; // do not kern twice (case: Calibri + arabic)
  413. else
  414. runInfo.Kernings[i - 1] = widths[i - 1] - gwidths[i - 1] + gOffset[i].du;
  415. }
  416. else
  417. {
  418. runInfo.ZeroOffsetX = gOffset[i].du * font.Size / sourceFont.Size;
  419. }
  420. runInfo.Kernings[i] = widths[i] - gwidths[i] - gOffset[i].du;
  421. // this should be converted to objfont units
  422. runInfo.VerticalOffsets[i] = -gOffset[i].dv * font.Size / sourceFont.Size;
  423. }
  424. }
  425. return runInfo;
  426. }
  427. private RunInfo GetRunInfoNonComplex(string text, Font originalFont)
  428. {
  429. int numGlyphs;
  430. ushort[] glyphs;
  431. float[] widths;
  432. float[] kernings;
  433. using (var ttfState = new TTFState(sourceFont))
  434. {
  435. numGlyphs = ttfState.Value.GetGlyphIndices(text, 1000, out glyphs, out widths, out kernings, false);
  436. }
  437. RunInfo runInfo = new RunInfo(numGlyphs)
  438. {
  439. Font = originalFont
  440. };
  441. for (int i = 0; i < numGlyphs; i++)
  442. {
  443. runInfo.Glyphs[i] = glyphs[i];
  444. runInfo.Widths[i] = (float)Math.Round(widths[i]);
  445. runInfo.GlyphToUnicode[i] = text[i].ToString();
  446. }
  447. return runInfo;
  448. }
  449. private int[] GetGlyphWidthsSlow(IntPtr hdc, int numGlyphs, ushort[] glyphs)
  450. {
  451. int[] gwidths = new int[numGlyphs];
  452. // this api is slow, avoid using it at any cost
  453. GetCharWidthI(hdc, 0, numGlyphs, glyphs, gwidths);
  454. return gwidths;
  455. }
  456. private int[] GetGlyphWidths(IntPtr hdc, int numGlyphs, ushort[] glyphs)
  457. {
  458. int[] glyphWidths = new int[numGlyphs];
  459. for (int i = 0; i < numGlyphs; i++)
  460. {
  461. ushort glyph = glyphs[i];
  462. if (usedGlyphChars.TryGetValue(glyph, out GlyphChar glyphChar))
  463. {
  464. // get existing glyph width
  465. glyphWidths[i] = (int)(glyphChar.Width);
  466. }
  467. else
  468. {
  469. // use slow api to get single glyph width.
  470. // Note that it is better to get all missing glyphs at a time; but in real word this won't be any faster
  471. glyphWidths[i] = GetGlyphWidthsSlow(hdc, 1, new ushort[] { glyph })[0];
  472. }
  473. }
  474. return glyphWidths;
  475. }
  476. private List<Run> Itemize(string s, bool rtl)
  477. {
  478. int maxItems = s.Length * 2;
  479. SCRIPT_ITEM[] pItems = new SCRIPT_ITEM[maxItems];
  480. int pcItems = 0;
  481. // initialize Control and State
  482. SCRIPT_CONTROL control = new SCRIPT_CONTROL();
  483. SCRIPT_STATE state = new SCRIPT_STATE();
  484. if (rtl)
  485. {
  486. // this is needed to start paragraph from right
  487. state.SetRtl();
  488. // to substitute arabic digits
  489. ScriptApplyDigitSubstitution(ref digitSubstitute, ref control, ref state);
  490. }
  491. // itemize
  492. ScriptItemize(s, s.Length, pItems.Length, ref control, ref state, pItems, ref pcItems);
  493. // create Run list. Note that ScriptItemize actually returns pcItems+1 items,
  494. // so this can be used to calculate char range easily
  495. List<Run> list = new List<Run>();
  496. for (int i = 0; i < pcItems; i++)
  497. {
  498. string text = s.Substring(pItems[i].iCharPos, pItems[i + 1].iCharPos - pItems[i].iCharPos);
  499. list.Add(new Run(text, pItems[i].analysis));
  500. }
  501. return list;
  502. }
  503. private List<Run> Layout(List<Run> runs)
  504. {
  505. byte[] pbLevel = new byte[runs.Count];
  506. int[] piVisualToLogical = new int[runs.Count];
  507. // build the pbLevel array
  508. for (int i = 0; i < runs.Count; i++)
  509. pbLevel[i] = (byte)runs[i].analysis.state.uBidiLevel;
  510. // layout runs
  511. ScriptLayout(runs.Count, pbLevel, piVisualToLogical, null);
  512. // return runs in their visual order
  513. List<Run> visualRuns = new List<Run>();
  514. for (int i = 0; i < piVisualToLogical.Length; i++)
  515. visualRuns.Add(runs[piVisualToLogical[i]]);
  516. return visualRuns;
  517. }
  518. private List<RunInfo> LayoutString(string str, bool rtl, Font originalFont)
  519. {
  520. List<RunInfo> result = new List<RunInfo>();
  521. bool isComplex = ScriptIsComplex(str, str.Length, 1 /*SIC_COMPLEX*/) != 1;
  522. if (!isComplex)
  523. {
  524. RunInfo runInfo = GetRunInfoNonComplex(str, originalFont);
  525. result.Add(runInfo);
  526. return result;
  527. }
  528. using (Bitmap b = new Bitmap(1, 1))
  529. {
  530. using (Graphics g = Graphics.FromImage(b))
  531. {
  532. IntPtr hdc = g.GetHdc();
  533. try
  534. {
  535. List<Run> runs = Itemize(str, rtl);
  536. runs = Layout(runs);
  537. float offsetX = 0;
  538. foreach (Run run in runs)
  539. {
  540. RunInfo runInfo = GetRunInfo(hdc, run, originalFont, sourceFont);
  541. offsetX += runInfo.ZeroOffsetX;
  542. runInfo.OffsetX = offsetX;
  543. offsetX += (runInfo.Widths.Sum() + runInfo.Kernings.Sum()) * runInfo.Font.Size / sourceFont.Size;
  544. result.Add(runInfo);
  545. }
  546. return result;
  547. }
  548. finally
  549. {
  550. g.ReleaseHdc(hdc);
  551. }
  552. }
  553. }
  554. }
  555. private void InternalInit()
  556. {
  557. dpiFX = 96f / DrawUtils.ScreenDpi;
  558. digitSubstitute = new SCRIPT_DIGITSUBSTITUTE();
  559. ScriptRecordDigitSubstitution(0x0400, ref digitSubstitute);
  560. }
  561. private void InternalDispose()
  562. {
  563. cache.Clear();
  564. }
  565. #endregion
  566. #region Private classes
  567. private class Run
  568. {
  569. public SCRIPT_ANALYSIS analysis;
  570. public string text;
  571. public Run(string text, SCRIPT_ANALYSIS a)
  572. {
  573. this.text = text;
  574. this.analysis = a;
  575. }
  576. }
  577. #if !CACHED_POINTERS
  578. private class Cache
  579. {
  580. private Dictionary<Font, CacheItem> cache = new Dictionary<Font, CacheItem>();
  581. public CacheItem GetCacheItem(IntPtr hdc, Font f)
  582. {
  583. lock (cache)
  584. {
  585. CacheItem cacheItem;
  586. if (!cache.TryGetValue(f, out cacheItem))
  587. {
  588. cacheItem = new CacheItem() { hFont = f.ToHfont(), usCache = IntPtr.Zero };
  589. cache.Add(f, cacheItem);
  590. }
  591. SelectObject(hdc, cacheItem.hFont);
  592. return cacheItem;
  593. }
  594. }
  595. public void Clear()
  596. {
  597. var values = cache.Values.ToArray();
  598. for (int i = 0; i < values.Length; i++)
  599. {
  600. var item = values[i];
  601. ScriptFreeCache(ref item.usCache);
  602. DeleteObject(item.hFont);
  603. }
  604. cache.Clear();
  605. }
  606. }
  607. private class CacheItem
  608. {
  609. public IntPtr hFont;
  610. public IntPtr usCache;
  611. }
  612. #endif
  613. #endregion
  614. }
  615. }
  616. #endif