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()); } }
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()); } }
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()); } }
public static string GetHtml(Scintilla scintilla, int startBytePos, int endBytePos) { // If we ever allow more than UTF-8, this will have to be revisited Debug.Assert(scintilla.DirectMessage(NativeMethods.SCI_GETCODEPAGE).ToInt32() == NativeMethods.SC_CP_UTF8); if (startBytePos == endBytePos) return string.Empty; StyleData[] styles = null; List<ArraySegment<byte>> styledSegments = GetStyledSegments(scintilla, false, false, startBytePos, endBytePos, out styles); using (var ms = new NativeMemoryStream(styledSegments.Sum(s => s.Count))) // Hint using (var sw = new StreamWriter(ms, new UTF8Encoding(false))) { // Write the styles sw.WriteLine(@"<style type=""text/css"" scoped="""">"); sw.Write("div#segments {"); sw.Write(" float: left;"); sw.Write(" white-space: pre;"); sw.Write(" line-height: {0}px;", scintilla.DirectMessage(NativeMethods.SCI_TEXTHEIGHT, new IntPtr(0)).ToInt32()); sw.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); sw.WriteLine(" }"); for (int i = 0; i < styles.Length; i++) { if (!styles[i].Used) continue; sw.Write("span.s{0} {{", i); sw.Write(@" font-family: ""{0}"";", styles[i].FontName); sw.Write(" font-size: {0}pt;", styles[i].SizeF); sw.Write(" font-weight: {0};", styles[i].Weight); if (styles[i].Italic != 0) sw.Write(" font-style: italic;"); if (styles[i].Underline != 0) sw.Write(" text-decoration: underline;"); sw.Write(" background-color: #{0:X2}{1:X2}{2:X2};", (styles[i].BackColor >> 0) & 0xFF, (styles[i].BackColor >> 8) & 0xFF, (styles[i].BackColor >> 16) & 0xFF); sw.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: sw.Write(" text-transform: uppercase;"); break; case StyleCase.Lower: sw.Write(" text-transform: lowercase;"); break; } if (styles[i].Visible == 0) sw.Write(" visibility: hidden;"); sw.WriteLine(" }"); } sw.WriteLine("</style>"); var unicodeLineEndings = ((scintilla.DirectMessage(NativeMethods.SCI_GETLINEENDTYPESACTIVE).ToInt32() & NativeMethods.SC_LINE_END_TYPE_UNICODE) > 0); var tabSize = scintilla.DirectMessage(NativeMethods.SCI_GETTABWIDTH).ToInt32(); var tab = new string(' ', tabSize); var lastStyle = Style.Default; // Write the styled text sw.Write(@"<div id=""segments""><span class=""s{0}"">", Style.Default); sw.Flush(); sw.AutoFlush = true; 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) { sw.Write(@"</span><span class=""s{0}"">", style); lastStyle = style; } switch (ch) { case (byte)'<': sw.Write("<"); break; case (byte)'>': sw.Write(">"); break; case (byte)'&': sw.Write("&"); break; case (byte)'\t': sw.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 sw.Write("\r\n"); break; default: if (ch == 0) { // Replace NUL with space sw.Write(" "); break; } ms.WriteByte(ch); break; } } } sw.AutoFlush = false; sw.WriteLine("</span></div>"); sw.Flush(); return GetString(ms.Pointer, (int)ms.Length, Encoding.UTF8); } }