in Office365APIEditor/External/ScintillaNET/Helpers.cs [230:459]
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());
}
}