ExportFont.Uniscribe.cs 21 KB

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