/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System.Runtime.CompilerServices; namespace dnSpy.Contracts.DnSpy.Text.WPF { /// /// Workaround for a WPF bug that terminates the process if any WPF control tries to format /// a string with too many combining marks. /// Test string: new string('\u0300', 512) /// static class WpfUnicodeUtils { // The real limit seems to be 512 public const int MAX_BAD_CHARS = 200; // See below for code that detects the bad values [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsBadWpfFormatterChar(uint cp) => cp >= 0x0300 && (cp <= 0x036F || (cp >= 0x0483 && cp <= 0x0489) || (cp >= 0x064B && cp <= 0x0655) || cp == 0x0670 || (cp >= 0x0816 && cp <= 0x0819) || (cp >= 0x081B && cp <= 0x0823) || (cp >= 0x0825 && cp <= 0x0827) || (cp >= 0x0829 && cp <= 0x082D) || (cp >= 0x0859 && cp <= 0x085B) || (cp >= 0x0951 && cp <= 0x0952) || (cp >= 0x0AFA && cp <= 0x0AFF) || cp == 0x0D00 || (cp >= 0x0D3B && cp <= 0x0D3C) || (cp >= 0x135D && cp <= 0x135F) || (cp >= 0x1AB0 && cp <= 0x1ABE) || (cp >= 0x1CD0 && cp <= 0x1CD2) || (cp >= 0x1CD4 && cp <= 0x1CE8) || cp == 0x1CED || (cp >= 0x1CF2 && cp <= 0x1CF4) || (cp >= 0x1CF7 && cp <= 0x1CF9) || (cp >= 0x1DC0 && cp <= 0x1DF9) || (cp >= 0x1DFB && cp <= 0x1DFF) || cp == 0x200D || (cp >= 0x20D0 && cp <= 0x20F0) || (cp >= 0x2CEF && cp <= 0x2CF1) || (cp >= 0x2DE0 && cp <= 0x2DFF) || (cp >= 0x302A && cp <= 0x302D) || (cp >= 0x3099 && cp <= 0x309A) || (cp >= 0xA66F && cp <= 0xA672) || (cp >= 0xA674 && cp <= 0xA67D) || (cp >= 0xA69E && cp <= 0xA69F) || (cp >= 0xA6F0 && cp <= 0xA6F1) || (cp >= 0xA8E0 && cp <= 0xA8F1) || (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xFE20 && cp <= 0xFE2F) || cp == 0x101FD || cp == 0x102E0 || (cp >= 0x10376 && cp <= 0x1037A) || (cp >= 0x1171D && cp <= 0x1172B) || (cp >= 0x16AF0 && cp <= 0x16AF4) || (cp >= 0x16F51 && cp <= 0x16F7E) || (cp >= 0x16F8F && cp <= 0x16F92) || (cp >= 0x1D165 && cp <= 0x1D169) || (cp >= 0x1D16D && cp <= 0x1D172) || (cp >= 0x1D17B && cp <= 0x1D182) || (cp >= 0x1D185 && cp <= 0x1D18B) || (cp >= 0x1D1AA && cp <= 0x1D1AD) || (cp >= 0x1D242 && cp <= 0x1D244) || (cp >= 0x1DA00 && cp <= 0x1DA36) || (cp >= 0x1DA3B && cp <= 0x1DA6C) || cp == 0x1DA75 || cp == 0x1DA84 || (cp >= 0x1DA9B && cp <= 0x1DA9F) || (cp >= 0x1DAA1 && cp <= 0x1DAAF) || (cp >= 0x1E000 && cp <= 0x1E006) || (cp >= 0x1E008 && cp <= 0x1E018) || (cp >= 0x1E01B && cp <= 0x1E021) || (cp >= 0x1E023 && cp <= 0x1E024) || (cp >= 0x1E026 && cp <= 0x1E02A) || (cp >= 0x1E8D0 && cp <= 0x1E8D6) || (cp >= 0xE0100 && cp <= 0xE01EF)); public static string ReplaceBadChars(string s) { bool hasBadChar = false; for (int i = 0; i < s.Length; i++) { uint cp = s[i]; if (char.IsHighSurrogate((char)cp) && i + 1 < s.Length) { uint lo = s[i + 1]; if (char.IsLowSurrogate((char)lo)) { cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); i++; } } if (IsBadWpfFormatterChar(cp)) { hasBadChar = true; break; } } if (!hasBadChar) return s; var chars = new char[s.Length]; int badChars = 0; for (int i = 0; i < s.Length; i++) { char hi = s[i]; var lo = (char)0; uint cp = hi; if (char.IsHighSurrogate((char)cp) && i + 1 < s.Length) { lo = s[i + 1]; uint ls = lo; if (char.IsLowSurrogate((char)ls)) cp = 0x10000 + ((cp - 0xD800) << 10) + (ls - 0xDC00); } if (IsBadWpfFormatterChar(cp)) { badChars++; if (badChars == MAX_BAD_CHARS) { badChars = 0; lo = hi = '?'; } } chars[i] = hi; if (cp > 0xFFFF) { i++; chars[i] = lo; } } return new string(chars); } } } #if false using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Media.TextFormatting; namespace WpfUnicode { /// /// Build it, and run it, attach a debugger (eg. WinDbg) since it crashes if there's no native /// debugger attached. When the debugger stops, hit continue (eg. hold down F5). /// sealed class Program { readonly TextRunPropertiesImpl textProps; readonly TextParagraphProperties paraProps; readonly TextFormatter[] formatters; readonly List codePoints; readonly List badCodePoints; [STAThread] public static void Main() { Console.WriteLine("Attach to the process with WinDbg and press any key to continue"); Console.ReadKey(); new Program().DoIt(); while (true) { Console.WriteLine("DONE"); Console.ReadKey(); } } Program() { textProps = new TextRunPropertiesImpl { typeface = new Typeface("Consolas"), fontRenderingEmSize = 13, fontHintingEmSize = 16, textDecorations = null, foregroundBrush = Brushes.Black, backgroundBrush = Brushes.White, cultureInfo = null, textEffects = null, }; formatters = new TextFormatter[] { TextFormatter.Create(TextFormattingMode.Ideal), TextFormatter.Create(TextFormattingMode.Display), }; paraProps = new TextParagraphPropertiesImpl(textProps); codePoints = new List(); badCodePoints = new List(); const uint MAX = 0xFFFF; for (int i = 0; i <= (int)MAX; i++) codePoints.Add((uint)i); // Latest version: https://www.unicode.org/Public/zipped/latest/UCD.zip foreach (var line in File.ReadAllLines(@"C:\UCD\extracted\DerivedName.txt")) { if (line.StartsWith("#") || line.Length == 0) continue; var s = line.Substring(0, line.IndexOf(' ')); var ss = s.Split(new[] { ".." }, StringSplitOptions.None); if (ss.Length == 1) { uint cp = uint.Parse(s, NumberStyles.HexNumber, null); if (cp > MAX) codePoints.Add(cp); } else if (ss.Length == 2) { uint lo = uint.Parse(ss[0], NumberStyles.HexNumber, null); uint hi = uint.Parse(ss[1], NumberStyles.HexNumber, null); while (lo <= hi) { if (lo > MAX) codePoints.Add(lo); lo++; } } else throw new InvalidOperationException(); } codePoints.Sort(); } void DoIt() { const int MAX_CHARS = 2048; var charData = new char[1 + MAX_CHARS * 2]; foreach (var cp in codePoints) { int i = 0; charData[i++] = 'a'; if (cp <= 0xFFFF) { for (int j = 0; j < MAX_CHARS; j++) charData[i++] = (char)cp; } else { uint v = cp - 0x10000; char lo = (char)(0xDC00 + (v & 0x3FF)); char hi = (char)(0xD800 + ((v >> 10) & 0x3FF)); for (int j = 0; j < MAX_CHARS; j++) { charData[i++] = hi; charData[i++] = lo; } } var s = new string(charData, 0, i); if (!FormatText(s)) { badCodePoints.Add(cp); Console.WriteLine($"U+{cp:X4}"); } } Write(badCodePoints); } bool FormatText(string text) { try { foreach (var formatter in formatters) { var textSource = new TextSourceImpl(text, textProps); formatter.FormatLine(textSource, 0, 1000000, paraProps, null); } return true; } catch (Exception) { return false; } } static void Write(List badCps) { for (int i = 0; i < badCps.Count; i++) { var cp = badCps[i]; var hi = cp; while (i + 1 < badCps.Count && hi + 1 == badCps[i + 1]) { i++; hi++; } if (cp == hi) Console.WriteLine($"cp == 0x{cp:X4} ||"); else Console.WriteLine($"(cp >= 0x{cp:X4} && cp <= 0x{hi:X4}) ||"); } } } sealed class TextSourceImpl : TextSource { readonly string text; readonly TextRunProperties textProps; public TextSourceImpl(string text, TextRunProperties textProps) { this.text = text; this.textProps = textProps; } public override TextSpan GetPrecedingText(int textSourceCharacterIndexLimit) => new TextSpan(0, new CultureSpecificCharacterBufferRange(CultureInfo.CurrentUICulture, new CharacterBufferRange(string.Empty, 0, 0))); public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex(int textSourceCharacterIndex) => textSourceCharacterIndex; public override TextRun GetTextRun(int textSourceCharacterIndex) { int length = text.Length - textSourceCharacterIndex; if (length == 0) return endOfLine; return new TextCharacters(text, textSourceCharacterIndex, length, textProps); } static readonly TextEndOfLine endOfLine = new TextEndOfLine(1); } sealed class TextRunPropertiesImpl : TextRunProperties { public override Typeface Typeface => typeface; public override double FontRenderingEmSize => fontRenderingEmSize; public override double FontHintingEmSize => fontHintingEmSize; public override TextDecorationCollection TextDecorations => textDecorations; public override Brush ForegroundBrush => foregroundBrush; public override Brush BackgroundBrush => backgroundBrush; public override CultureInfo CultureInfo => CultureInfo.CurrentUICulture; public override TextEffectCollection TextEffects => textEffects; public Typeface typeface; public double fontRenderingEmSize; public double fontHintingEmSize; public TextDecorationCollection textDecorations; public Brush foregroundBrush; public Brush backgroundBrush; public CultureInfo cultureInfo; public TextEffectCollection textEffects; } sealed class TextParagraphPropertiesImpl : TextParagraphProperties { public override FlowDirection FlowDirection => FlowDirection.LeftToRight; public override TextAlignment TextAlignment => TextAlignment.Left; public override double LineHeight => 0; public override bool FirstLineInParagraph => false; public override TextRunProperties DefaultTextRunProperties { get; } public override TextWrapping TextWrapping => TextWrapping.Wrap; public override TextMarkerProperties TextMarkerProperties => null; public override double Indent => 0; public override double DefaultIncrementalTab => 28; public TextParagraphPropertiesImpl(TextRunProperties textProps) => DefaultTextRunProperties = textProps; } } #endif