in Office365APIEditor/External/ScintillaNET/Helpers.cs [461:742]
private static unsafe void CopyRtf(Scintilla scintilla, StyleData[] styles, List<ArraySegment<byte>> styledSegments)
{
// NppExport -> NppExport.cpp
// NppExport -> RTFExporter.cpp
// http://en.wikipedia.org/wiki/Rich_Text_Format
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms649013.aspx
// http://forums.codeguru.com/showthread.php?242982-Converting-pixels-to-twips
// http://en.wikipedia.org/wiki/UTF-8
try
{
// Calculate twips per space
int twips;
var fontStyle = FontStyle.Regular;
if (styles[Style.Default].Weight >= 700)
fontStyle |= FontStyle.Bold;
if (styles[Style.Default].Italic != 0)
fontStyle |= FontStyle.Italic;
if (styles[Style.Default].Underline != 0)
fontStyle |= FontStyle.Underline;
using (var graphics = scintilla.CreateGraphics())
using (var font = new Font(styles[Style.Default].FontName, styles[Style.Default].SizeF, fontStyle))
{
var width = graphics.MeasureString(" ", font).Width;
twips = (int)((width / graphics.DpiX) * 1440);
// TODO The twips value calculated seems too small on my computer
}
// Write RTF
using (var ms = new NativeMemoryStream(styledSegments.Sum(s => s.Count)))
using (var tw = new StreamWriter(ms, Encoding.ASCII))
{
var tabWidth = scintilla.DirectMessage(NativeMethods.SCI_GETTABWIDTH).ToInt32();
var deftab = tabWidth * twips;
tw.WriteLine(@"{{\rtf1\ansi\deff0\deftab{0}", deftab);
tw.Flush();
// Build the font table
tw.Write(@"{\fonttbl");
tw.Write(@"{{\f0 {0};}}", styles[Style.Default].FontName);
var fontIndex = 1;
for (int i = 0; i < styles.Length; i++)
{
if (!styles[i].Used)
continue;
if (i == Style.Default)
continue;
// Not a completely unique list, but close enough
if (styles[i].FontName != styles[Style.Default].FontName)
{
styles[i].FontIndex = fontIndex++;
tw.Write(@"{{\f{0} {1};}}", styles[i].FontIndex, styles[i].FontName);
}
}
tw.WriteLine("}"); // fonttbl
tw.Flush();
// Build the color table
tw.Write(@"{\colortbl");
tw.Write(@"\red{0}\green{1}\blue{2};", (styles[Style.Default].ForeColor >> 0) & 0xFF, (styles[Style.Default].ForeColor >> 8) & 0xFF, (styles[Style.Default].ForeColor >> 16) & 0xFF);
tw.Write(@"\red{0}\green{1}\blue{2};", (styles[Style.Default].BackColor >> 0) & 0xFF, (styles[Style.Default].BackColor >> 8) & 0xFF, (styles[Style.Default].BackColor >> 16) & 0xFF);
styles[Style.Default].ForeColorIndex = 0;
styles[Style.Default].BackColorIndex = 1;
var colorIndex = 2;
for (int i = 0; i < styles.Length; i++)
{
if (!styles[i].Used)
continue;
if (i == Style.Default)
continue;
// Not a completely unique list, but close enough
if (styles[i].ForeColor != styles[Style.Default].ForeColor)
{
styles[i].ForeColorIndex = colorIndex++;
tw.Write(@"\red{0}\green{1}\blue{2};", (styles[i].ForeColor >> 0) & 0xFF, (styles[i].ForeColor >> 8) & 0xFF, (styles[i].ForeColor >> 16) & 0xFF);
}
else
{
styles[i].ForeColorIndex = styles[Style.Default].ForeColorIndex;
}
if (styles[i].BackColor != styles[Style.Default].BackColor)
{
styles[i].BackColorIndex = colorIndex++;
tw.Write(@"\red{0}\green{1}\blue{2};", (styles[i].BackColor >> 0) & 0xFF, (styles[i].BackColor >> 8) & 0xFF, (styles[i].BackColor >> 16) & 0xFF);
}
else
{
styles[i].BackColorIndex = styles[Style.Default].BackColorIndex;
}
}
tw.WriteLine("}"); // colortbl
tw.Flush();
// Start with the default style
tw.Write(@"\f{0}\fs{1}\cf{2}\chshdng0\chcbpat{3}\cb{3} ", styles[Style.Default].FontIndex, (int)(styles[Style.Default].SizeF * 2), styles[Style.Default].ForeColorIndex, styles[Style.Default].BackColorIndex);
if (styles[Style.Default].Italic != 0)
tw.Write(@"\i");
if (styles[Style.Default].Underline != 0)
tw.Write(@"\ul");
if (styles[Style.Default].Weight >= 700)
tw.Write(@"\b");
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)
{
// Change the style
if (styles[lastStyle].FontIndex != styles[style].FontIndex)
tw.Write(@"\f{0}", styles[style].FontIndex);
if (styles[lastStyle].SizeF != styles[style].SizeF)
tw.Write(@"\fs{0}", (int)(styles[style].SizeF * 2));
if (styles[lastStyle].ForeColorIndex != styles[style].ForeColorIndex)
tw.Write(@"\cf{0}", styles[style].ForeColorIndex);
if (styles[lastStyle].BackColorIndex != styles[style].BackColorIndex)
tw.Write(@"\chshdng0\chcbpat{0}\cb{0}", styles[style].BackColorIndex);
if (styles[lastStyle].Italic != styles[style].Italic)
tw.Write(@"\i{0}", styles[style].Italic != 0 ? "" : "0");
if (styles[lastStyle].Underline != styles[style].Underline)
tw.Write(@"\ul{0}", styles[style].Underline != 0 ? "" : "0");
if (styles[lastStyle].Weight != styles[style].Weight)
{
if (styles[style].Weight >= 700 && styles[lastStyle].Weight < 700)
tw.Write(@"\b");
else if (styles[style].Weight < 700 && styles[lastStyle].Weight >= 700)
tw.Write(@"\b0");
}
// NOTE: We don't support StyleData.Visible and StyleData.Case in RTF
lastStyle = style;
tw.Write(" "); // Delimiter
}
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.WriteLine(@"\par");
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;
}
if (ch > 0x7F)
{
// Treat as UTF-8 code point
int unicode = 0;
if (ch < 0xE0 && i + 2 < endOffset)
{
unicode |= ((0x1F & ch) << 6);
unicode |= (0x3F & seg.Array[i + 2]);
tw.Write(@"\u{0}?", unicode);
i += 2;
break;
}
else if (ch < 0xF0 && i + 4 < endOffset)
{
unicode |= ((0xF & ch) << 12);
unicode |= ((0x3F & seg.Array[i + 2]) << 6);
unicode |= (0x3F & seg.Array[i + 4]);
tw.Write(@"\u{0}?", unicode);
i += 4;
break;
}
else if (ch < 0xF8 && i + 6 < endOffset)
{
unicode |= ((0x7 & ch) << 18);
unicode |= ((0x3F & seg.Array[i + 2]) << 12);
unicode |= ((0x3F & seg.Array[i + 4]) << 6);
unicode |= (0x3F & seg.Array[i + 6]);
tw.Write(@"\u{0}?", unicode);
i += 6;
break;
}
}
// Regular ANSI char
ms.WriteByte(ch);
break;
}
}
}
tw.AutoFlush = false;
tw.WriteLine("}"); // rtf1
tw.Flush();
// Terminator
ms.WriteByte(0);
// var str = GetString(ms.Pointer, (int)ms.Length, Encoding.ASCII);
if (NativeMethods.SetClipboardData(CF_RTF, 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());
}
}