1+ // ==========================================================================================
2+ // GameFrameX 组织及其衍生项目的版权、商标、专利及其他相关权利
3+ // GameFrameX organization and its derivative projects' copyrights, trademarks, patents, and related rights
4+ // 均受中华人民共和国及相关国际法律法规保护。
5+ // are protected by the laws of the People's Republic of China and relevant international regulations.
6+ //
7+ // 使用本项目须严格遵守相应法律法规及开源许可证之规定。
8+ // Usage of this project must strictly comply with applicable laws, regulations, and open-source licenses.
9+ //
10+ // 本项目采用 MIT 许可证与 Apache License 2.0 双许可证分发,
11+ // This project is dual-licensed under the MIT License and Apache License 2.0,
12+ // 完整许可证文本请参见源代码根目录下的 LICENSE 文件。
13+ // please refer to the LICENSE file in the root directory of the source code for the full license text.
14+ //
15+ // 禁止利用本项目实施任何危害国家安全、破坏社会秩序、
16+ // It is prohibited to use this project to engage in any activities that endanger national security, disrupt social order,
17+ // 侵犯他人合法权益等法律法规所禁止的行为!
18+ // or infringe upon the legitimate rights and interests of others, as prohibited by laws and regulations!
19+ // 因基于本项目二次开发所产生的一切法律纠纷与责任,
20+ // Any legal disputes and liabilities arising from secondary development based on this project
21+ // 本项目组织与贡献者概不承担。
22+ // shall be borne solely by the developer; the project organization and contributors assume no responsibility.
23+ //
24+ // GitHub 仓库:https://github.com/GameFrameX
25+ // GitHub Repository: https://github.com/GameFrameX
26+ // Gitee 仓库:https://gitee.com/GameFrameX
27+ // Gitee Repository: https://gitee.com/GameFrameX
28+ // 官方文档:https://gameframex.doc.alianblank.com/
29+ // Official Documentation: https://gameframex.doc.alianblank.com/
30+ // ==========================================================================================
31+
32+ using System . Text ;
33+ using System . Text . Encodings . Web ;
34+
35+ namespace GameFrameX . Foundation . Json ;
36+
37+ internal sealed class UnicodeJsonEncoder : JavaScriptEncoder
38+ {
39+ internal static readonly UnicodeJsonEncoder Singleton = new UnicodeJsonEncoder ( ) ;
40+
41+ private readonly bool _preferHexEscape ;
42+ private readonly bool _preferUppercase ;
43+
44+ private UnicodeJsonEncoder ( ) : this ( preferHexEscape : false , preferUppercase : false )
45+ {
46+ }
47+
48+ private UnicodeJsonEncoder ( bool preferHexEscape , bool preferUppercase )
49+ {
50+ _preferHexEscape = preferHexEscape ;
51+ _preferUppercase = preferUppercase ;
52+ }
53+
54+ public override int MaxOutputCharactersPerInputCharacter
55+ {
56+ get
57+ {
58+ return 6 ;
59+ // "\uXXXX" for a single char ("\uXXXX\uYYYY" [12 chars] for supplementary scalar value)
60+ }
61+ }
62+
63+ public override unsafe int FindFirstCharacterToEncode ( char * text , int textLength )
64+ {
65+ for ( int index = 0 ; index < textLength ; ++ index )
66+ {
67+ char value = text [ index ] ;
68+
69+ if ( NeedsEncoding ( value ) )
70+ {
71+ return index ;
72+ }
73+ }
74+
75+ return - 1 ;
76+ }
77+
78+ public override unsafe bool TryEncodeUnicodeScalar ( int unicodeScalar , char * buffer , int bufferLength , out int numberOfCharactersWritten )
79+ {
80+ bool encode = WillEncode ( unicodeScalar ) ;
81+
82+ if ( ! encode )
83+ {
84+ Span < char > span = new Span < char > ( buffer , bufferLength ) ;
85+ bool succeeded = new Rune ( unicodeScalar ) . TryEncodeToUtf16 ( span , out var spanWritten ) ;
86+ numberOfCharactersWritten = spanWritten ;
87+ return succeeded ;
88+ }
89+
90+ if ( ! _preferHexEscape && unicodeScalar <= char . MaxValue && HasTwoCharacterEscape ( ( char ) unicodeScalar ) )
91+ {
92+ if ( bufferLength < 2 )
93+ {
94+ numberOfCharactersWritten = 0 ;
95+ return false ;
96+ }
97+
98+ buffer [ 0 ] = '\\ ' ;
99+ buffer [ 1 ] = GetTwoCharacterEscapeSuffix ( ( char ) unicodeScalar ) ;
100+ numberOfCharactersWritten = 2 ;
101+ return true ;
102+ }
103+ else
104+ {
105+ if ( bufferLength < 6 )
106+ {
107+ numberOfCharactersWritten = 0 ;
108+ return false ;
109+ }
110+
111+ buffer [ 0 ] = '\\ ' ;
112+ buffer [ 1 ] = 'u' ;
113+ buffer [ 2 ] = '0' ;
114+ buffer [ 3 ] = '0' ;
115+ buffer [ 4 ] = ToHexDigit ( ( unicodeScalar & 0xf0 ) >> 4 , _preferUppercase ) ;
116+ buffer [ 5 ] = ToHexDigit ( unicodeScalar & 0xf , _preferUppercase ) ;
117+ numberOfCharactersWritten = 6 ;
118+ return true ;
119+ }
120+ }
121+
122+ public override bool WillEncode ( int unicodeScalar )
123+ {
124+ if ( unicodeScalar > char . MaxValue )
125+ {
126+ return false ;
127+ }
128+
129+ return NeedsEncoding ( ( char ) unicodeScalar ) ;
130+ }
131+
132+ // https://datatracker.ietf.org/doc/html/rfc8259#section-7
133+ private static bool NeedsEncoding ( char value )
134+ {
135+ if ( value == '"' || value == '\\ ' )
136+ {
137+ return true ;
138+ }
139+
140+ return value <= '\u001f ' ;
141+ }
142+
143+ private static bool HasTwoCharacterEscape ( char value )
144+ {
145+ // RFC 8259, Section 7, "char = " BNF
146+ switch ( value )
147+ {
148+ case '"' :
149+ case '\\ ' :
150+ case '/' :
151+ case '\b ' :
152+ case '\f ' :
153+ case '\n ' :
154+ case '\r ' :
155+ case '\t ' :
156+ return true ;
157+ default :
158+ return false ;
159+ }
160+ }
161+
162+ private static char GetTwoCharacterEscapeSuffix ( char value )
163+ {
164+ // RFC 8259, Section 7, "char = " BNF
165+ switch ( value )
166+ {
167+ case '"' :
168+ return '"' ;
169+ case '\\ ' :
170+ return '\\ ' ;
171+ case '/' :
172+ return '/' ;
173+ case '\b ' :
174+ return 'b' ;
175+ case '\f ' :
176+ return 'f' ;
177+ case '\n ' :
178+ return 'n' ;
179+ case '\r ' :
180+ return 'r' ;
181+ case '\t ' :
182+ return 't' ;
183+ default :
184+ throw new ArgumentOutOfRangeException ( nameof ( value ) ) ;
185+ }
186+ }
187+
188+ private static char ToHexDigit ( int value , bool uppercase )
189+ {
190+ if ( value > 0xf )
191+ {
192+ throw new ArgumentOutOfRangeException ( nameof ( value ) ) ;
193+ }
194+
195+ if ( value < 10 )
196+ {
197+ return ( char ) ( value + '0' ) ;
198+ }
199+ else
200+ {
201+ return ( char ) ( value - 0xa + ( uppercase ? 'A' : 'a' ) ) ;
202+ }
203+ }
204+ }
0 commit comments