private static unsafe void CopyHtml(Scintilla scintilla, StyleData[] styles, List<ArraySegment<byte>> styledSegments) { // NppExport -> NppExport.cpp // NppExport -> HTMLExporter.cpp // http://blogs.msdn.com/b/jmstall/archive/2007/01/21/html-clipboard.aspx // http://blogs.msdn.com/b/jmstall/archive/2007/01/21/sample-code-html-clipboard.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ms649015.aspx try { long pos = 0; byte[] bytes; // Write HTML using (var ms = new NativeMemoryStream(styledSegments.Sum(s => s.Count))) using (var tw = new StreamWriter(ms, new UTF8Encoding(false))) { const int INDEX_START_HTML = 23; const int INDEX_START_FRAGMENT = 65; const int INDEX_END_FRAGMENT = 87; const int INDEX_END_HTML = 41; tw.WriteLine("Version:0.9"); tw.WriteLine("StartHTML:00000000"); tw.WriteLine("EndHTML:00000000"); tw.WriteLine("StartFragment:00000000"); tw.WriteLine("EndFragment:00000000"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_START_HTML, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<html>"); tw.WriteLine("<head>"); tw.WriteLine(@"<meta charset=""utf-8"" />"); tw.WriteLine(@"<title>ScintillaNET v{0}</title>", scintilla.GetType().Assembly.GetName().Version.ToString(3)); tw.WriteLine("</head>"); tw.WriteLine("<body>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_START_FRAGMENT, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<!--StartFragment -->"); // Write the styles. // We're doing the style tag in the body to include it in the "fragment". tw.WriteLine(@"<style type=""text/css"" scoped="""">"); tw.Write("div#segments {"); tw.Write(" float: left;"); tw.Write(" white-space: pre;"); tw.Write(" line-height: {0}px;", scintilla.DirectMessage(NativeMethods.SCI_TEXTHEIGHT, new IntPtr(0)).ToInt32()); tw.Write(" background-color: #{0:X2}{1:X2}{2:X2};", (styles[Style.Default].BackColor >> 0) & 0xFF, (styles[Style.Default].BackColor >> 8) & 0xFF, (styles[Style.Default].BackColor >> 16) & 0xFF); tw.WriteLine(" }"); for (int i = 0; i < styles.Length; i++) { if (!styles[i].Used) continue; tw.Write("span.s{0} {{", i); tw.Write(@" font-family: ""{0}"";", styles[i].FontName); tw.Write(" font-size: {0}pt;", styles[i].SizeF); tw.Write(" font-weight: {0};", styles[i].Weight); if (styles[i].Italic != 0) tw.Write(" font-style: italic;"); if (styles[i].Underline != 0) tw.Write(" text-decoration: underline;"); tw.Write(" background-color: #{0:X2}{1:X2}{2:X2};", (styles[i].BackColor >> 0) & 0xFF, (styles[i].BackColor >> 8) & 0xFF, (styles[i].BackColor >> 16) & 0xFF); tw.Write(" color: #{0:X2}{1:X2}{2:X2};", (styles[i].ForeColor >> 0) & 0xFF, (styles[i].ForeColor >> 8) & 0xFF, (styles[i].ForeColor >> 16) & 0xFF); switch ((StyleCase)styles[i].Case) { case StyleCase.Upper: tw.Write(" text-transform: uppercase;"); break; case StyleCase.Lower: tw.Write(" text-transform: lowercase;"); break; } if (styles[i].Visible == 0) tw.Write(" visibility: hidden;"); tw.WriteLine(" }"); } tw.WriteLine("</style>"); tw.Write(@"<div id=""segments""><span class=""s{0}"">", Style.Default); tw.Flush(); var tabSize = scintilla.DirectMessage(NativeMethods.SCI_GETTABWIDTH).ToInt32(); var tab = new string(' ', tabSize); tw.AutoFlush = true; var lastStyle = Style.Default; var unicodeLineEndings = ((scintilla.DirectMessage(NativeMethods.SCI_GETLINEENDTYPESACTIVE).ToInt32() & NativeMethods.SC_LINE_END_TYPE_UNICODE) > 0); foreach (var seg in styledSegments) { var endOffset = seg.Offset + seg.Count; for (int i = seg.Offset; i < endOffset; i += 2) { var ch = seg.Array[i]; var style = seg.Array[i + 1]; if (lastStyle != style) { tw.Write(@"</span><span class=""s{0}"">", style); lastStyle = style; } switch (ch) { case (byte)'<': tw.Write("<"); break; case (byte)'>': tw.Write(">"); break; case (byte)'&': tw.Write("&"); break; case (byte)'\t': tw.Write(tab); break; case (byte)'\r': if (i + 2 < endOffset) { if (seg.Array[i + 2] == (byte)'\n') i += 2; } // Either way, this is a line break goto case (byte)'\n'; case 0xC2: if (unicodeLineEndings && i + 2 < endOffset) { if (seg.Array[i + 2] == 0x85) // NEL \u0085 { i += 2; goto case (byte)'\n'; } } // Not a Unicode line break goto default; case 0xE2: if (unicodeLineEndings && i + 4 < endOffset) { if (seg.Array[i + 2] == 0x80 && seg.Array[i + 4] == 0xA8) // LS \u2028 { i += 4; goto case (byte)'\n'; } else if (seg.Array[i + 2] == 0x80 && seg.Array[i + 4] == 0xA9) // PS \u2029 { i += 4; goto case (byte)'\n'; } } // Not a Unicode line break goto default; case (byte)'\n': // All your line breaks are belong to us tw.Write("\r\n"); break; default: if (ch == 0) { // Scintilla behavior is to allow control characters except for // NULL which will cause the Clipboard to truncate the string. tw.Write(" "); // Replace with space break; } ms.WriteByte(ch); break; } } } tw.AutoFlush = false; tw.WriteLine("</span></div>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_END_FRAGMENT, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<!--EndFragment-->"); tw.WriteLine("</body>"); tw.WriteLine("</html>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_END_HTML, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); // Terminator ms.WriteByte(0); var str = GetString(ms.Pointer, (int)ms.Length, Encoding.UTF8); if (NativeMethods.SetClipboardData(CF_HTML, ms.Pointer) != IntPtr.Zero) ms.FreeOnDispose = false; // Clipboard will free memory } } catch (Exception ex) { // Yes, we swallow any exceptions. That may seem like code smell but this matches // the behavior of the Clipboard class, Windows Forms controls, and native Scintilla. Debug.Fail(ex.Message, ex.ToString()); } }
private static unsafe void CopyHtml(Scintilla scintilla, StyleData[] styles, List <ArraySegment <byte> > styledSegments) { // NppExport -> NppExport.cpp // NppExport -> HTMLExporter.cpp // http://blogs.msdn.com/b/jmstall/archive/2007/01/21/html-clipboard.aspx // http://blogs.msdn.com/b/jmstall/archive/2007/01/21/sample-code-html-clipboard.aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ms649015.aspx try { long pos = 0; byte[] bytes; // Write HTML using (var ms = new NativeMemoryStream(styledSegments.Sum(s => s.Count))) using (var tw = new StreamWriter(ms, new UTF8Encoding(false))) { const int INDEX_START_HTML = 23; const int INDEX_START_FRAGMENT = 65; const int INDEX_END_FRAGMENT = 87; const int INDEX_END_HTML = 41; tw.WriteLine("Version:0.9"); tw.WriteLine("StartHTML:00000000"); tw.WriteLine("EndHTML:00000000"); tw.WriteLine("StartFragment:00000000"); tw.WriteLine("EndFragment:00000000"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_START_HTML, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<html>"); tw.WriteLine("<head>"); tw.WriteLine(@"<meta charset=""utf-8"" />"); tw.WriteLine(@"<title>ScintillaNET v{0}</title>", scintilla.GetType().Assembly.GetName().Version.ToString(3)); tw.WriteLine("</head>"); tw.WriteLine("<body>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_START_FRAGMENT, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<!--StartFragment -->"); // Write the styles. // We're doing the style tag in the body to include it in the "fragment". tw.WriteLine(@"<style type=""text/css"" scoped="""">"); tw.Write("div#segments {"); tw.Write(" float: left;"); tw.Write(" white-space: pre;"); tw.Write(" line-height: {0}px;", scintilla.DirectMessage(NativeMethods.SCI_TEXTHEIGHT, new IntPtr(0)).ToInt32()); tw.Write(" background-color: #{0:X2}{1:X2}{2:X2};", (styles[Style.Default].BackColor >> 0) & 0xFF, (styles[Style.Default].BackColor >> 8) & 0xFF, (styles[Style.Default].BackColor >> 16) & 0xFF); tw.WriteLine(" }"); for (int i = 0; i < styles.Length; i++) { if (!styles[i].Used) { continue; } tw.Write("span.s{0} {{", i); tw.Write(@" font-family: ""{0}"";", styles[i].FontName); tw.Write(" font-size: {0}pt;", styles[i].SizeF); tw.Write(" font-weight: {0};", styles[i].Weight); if (styles[i].Italic != 0) { tw.Write(" font-style: italic;"); } if (styles[i].Underline != 0) { tw.Write(" text-decoration: underline;"); } tw.Write(" background-color: #{0:X2}{1:X2}{2:X2};", (styles[i].BackColor >> 0) & 0xFF, (styles[i].BackColor >> 8) & 0xFF, (styles[i].BackColor >> 16) & 0xFF); tw.Write(" color: #{0:X2}{1:X2}{2:X2};", (styles[i].ForeColor >> 0) & 0xFF, (styles[i].ForeColor >> 8) & 0xFF, (styles[i].ForeColor >> 16) & 0xFF); switch ((StyleCase)styles[i].Case) { case StyleCase.Upper: tw.Write(" text-transform: uppercase;"); break; case StyleCase.Lower: tw.Write(" text-transform: lowercase;"); break; } if (styles[i].Visible == 0) { tw.Write(" visibility: hidden;"); } tw.WriteLine(" }"); } tw.WriteLine("</style>"); tw.Write(@"<div id=""segments""><span class=""s{0}"">", Style.Default); tw.Flush(); var tabSize = scintilla.DirectMessage(NativeMethods.SCI_GETTABWIDTH).ToInt32(); var tab = new string(' ', tabSize); tw.AutoFlush = true; var lastStyle = Style.Default; var unicodeLineEndings = ((scintilla.DirectMessage(NativeMethods.SCI_GETLINEENDTYPESACTIVE).ToInt32() & NativeMethods.SC_LINE_END_TYPE_UNICODE) > 0); foreach (var seg in styledSegments) { var endOffset = seg.Offset + seg.Count; for (int i = seg.Offset; i < endOffset; i += 2) { var ch = seg.Array[i]; var style = seg.Array[i + 1]; if (lastStyle != style) { tw.Write(@"</span><span class=""s{0}"">", style); lastStyle = style; } switch (ch) { case (byte)'<': tw.Write("<"); break; case (byte)'>': tw.Write(">"); break; case (byte)'&': tw.Write("&"); break; case (byte)'\t': tw.Write(tab); break; case (byte)'\r': if (i + 2 < endOffset) { if (seg.Array[i + 2] == (byte)'\n') { i += 2; } } // Either way, this is a line break goto case (byte)'\n'; case 0xC2: if (unicodeLineEndings && i + 2 < endOffset) { if (seg.Array[i + 2] == 0x85) // NEL \u0085 { i += 2; goto case (byte)'\n'; } } // Not a Unicode line break goto default; case 0xE2: if (unicodeLineEndings && i + 4 < endOffset) { if (seg.Array[i + 2] == 0x80 && seg.Array[i + 4] == 0xA8) // LS \u2028 { i += 4; goto case (byte)'\n'; } else if (seg.Array[i + 2] == 0x80 && seg.Array[i + 4] == 0xA9) // PS \u2029 { i += 4; goto case (byte)'\n'; } } // Not a Unicode line break goto default; case (byte)'\n': // All your line breaks are belong to us tw.Write("\r\n"); break; default: if (ch == 0) { // Scintilla behavior is to allow control characters except for // NULL which will cause the Clipboard to truncate the string. tw.Write(" "); // Replace with space break; } ms.WriteByte(ch); break; } } } tw.AutoFlush = false; tw.WriteLine("</span></div>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_END_FRAGMENT, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); tw.WriteLine("<!--EndFragment-->"); tw.WriteLine("</body>"); tw.WriteLine("</html>"); tw.Flush(); // Patch header pos = ms.Position; ms.Seek(INDEX_END_HTML, SeekOrigin.Begin); ms.Write((bytes = Encoding.ASCII.GetBytes(ms.Length.ToString("D8"))), 0, bytes.Length); ms.Seek(pos, SeekOrigin.Begin); // Terminator ms.WriteByte(0); var str = GetString(ms.Pointer, (int)ms.Length, Encoding.UTF8); if (NativeMethods.SetClipboardData(CF_HTML, ms.Pointer) != IntPtr.Zero) { ms.FreeOnDispose = false; // Clipboard will free memory } } } catch (Exception ex) { // Yes, we swallow any exceptions. That may seem like code smell but this matches // the behavior of the Clipboard class, Windows Forms controls, and native Scintilla. Debug.Fail(ex.Message, ex.ToString()); } }