マイクロエディター

世界には、記事の著者がWebリソースで公開するコンテンツを準備するのに役立つさまざまな便利なエディターがあります。 しかし、残念なことに、最高のものは善の敵であることが非常に頻繁に起こります。また、自分のエディターを望みの目的に合わせてシャープにする代わりに、あまり便利ではないプログラムを使用する必要があります。 この問題に対する私のアプローチは、独自のエディターを作成することです。 この投稿では、実装した機能と方法について説明します。

内容




コンテンツを自動生成


同じMicrosoft Wordを使用する場合、たとえば、テキストにコンテンツ(目次)またはリンク(参照/文末脚注/脚注)を追加する機能があります。 Webリソースで公開するには、これらの機能が必要になることがありますが、すべてのポータルが名前付きリンクなどをサポートしているわけではありません。 それでも、エディターに追加できた2つの機能は、TOCの自動生成とリンクのリスト(「メモ」)です。

内容


この投稿では、コンテンツアイテムのリストが少し上に表示されます。 その本質は、投稿または記事の要素をすばやくナビゲートできるようにすることです。 当然、このリストは、ドキュメント内で検出されたH1 - H8タグを使用して自動生成されます。 単純にそれらを切り離し、ヘッダーの最上位レベルを見つけ(たとえば、Habrの場合はH3 )、ネストされた要素ULおよびLIを使用して対応するリストを作成します。 これはあまり安全ではありませんが、このような単純な式はヘッダーの検索に使用されます。

var r = new Regex( "<h([^<]+)>(.+)</h.>" );<br/>

コンテンツは、たとえばCodeProjectに関する記事を書いている場合など、大規模な投稿で主に役立ちます。 ところで、見出しがない場合、コンテンツは作成されないことに注意してください。 また、コンテンツのヘッダーレベル(存在する場合)は、常にファイルのヘッダーのトップレベルに対応します。

このようなリストを実装するのは簡単なことではありませんでした。 これがどのように行われるかを示すコードの一部を次に示します。

private static string GenerateToc([NotNull] string text, ConversionOptions options, out int minLevel)<br/>
{<br/>
List<HeadingEntry> entries = new List<HeadingEntry>();<br/>
var r = new Regex( "<h([^<]+)>(.+)</h.>" );<br/>
var matches = r.Matches(text);<br/>
int count = 0;<br/>
foreach (Match m in matches)<br/>
{<br/>
int n;<br/>
if ( int .TryParse(m.Groups[1].Value, out n))<br/>
{<br/>
HeadingEntry he = new HeadingEntry(n, m.Groups[2].Value);<br/>
// set tag text if clash
bool bFound = false ;<br/>
foreach (HeadingEntry h in entries)<br/>
if (h.SuggestedTagText == he.SuggestedTagText)<br/>
bFound = true ;<br/>
he.TagText = he.SuggestedTagText + (bFound ? (count++).ToString() : string .Empty);<br/>
entries.Add(he);<br/>
// replace only first occurence
//text = text.Replace(m.Groups[0].Value,
// string.Format("<h{0}><a name=\"{2}\"></a>{1}</h{0}>", n, he.Text, he.TagText));
int idx = text.IndexOf(m.Groups[0].Value);<br/>
string emptyLink = options.UseImageHeadings ? string .Empty :<br/>
string .Format( "<a name=\"{0}\"></a>" , he.TagText);<br/>
text = text.Remove(idx, m.Groups[0].Value.Length).Insert(idx,<br/>
string .Format( "<h{0}>{2}{1}</h{0}>" , n, he.Text, emptyLink));<br/>
}<br/>
}<br/>
minLevel = int .MaxValue;<br/>
if (entries.Count > 0)<br/>
{<br/>
var hb = new HtmlBuilder();<br/>
// all are, essentially, ULs
int lastLevel = -1;<br/>
foreach (HeadingEntry he in entries)<br/>
{<br/>
// if this level > last, open a new UL
if (he.Level > lastLevel)<br/>
hb.AppendLine( "<ul>" ).Indent();<br/>
if (he.Level < lastLevel)<br/>
{<br/>
int diff = lastLevel - he.Level;<br/>
while (--diff >= 0)<br/>
hb.Unindent().AppendLine( "</ul>" );<br/>
}<br/>
hb.AppendLine( string .Format( "<li><a href=\"#{0}\">{1}</a></li>" , he.TagText, he.Text));<br/>
minLevel = Math.Min(minLevel, he.Level);<br/>
lastLevel = he.Level;<br/>
}<br/>
// close out all indent levels
while (hb.IndentLevel > 0)<br/>
hb.Unindent().AppendLine( "</ul>" );<br/>
if (! string .IsNullOrEmpty(options.TocLabel))<br/>
hb.Insert( string .Format( "<h{0}>{1}</h{0}>{2}" , minLevel,<br/>
HttpUtility.HtmlEncode(options.TocLabel), Environment.NewLine), 0);<br/>
// at this point, hb contains the TOC, so
if (! string .IsNullOrEmpty(options.TocAfterToken))<br/>
{<br/>
int idx = text.IndexOf(options.TocAfterToken);<br/>
if (idx >= 0)<br/>
return text.Substring(0, idx + options.TocAfterToken.Length) +<br/>
hb + text.Substring(idx + options.TocAfterToken.Length);<br/>
}<br/>
hb.Append(text);<br/>
return hb.ToString();<br/>
}<br/>
return text;<br/>
}<br/>

注釈


メモまたはリンクは、記事自体の後に表示される追加の重要でない考えです。 [ ]角括弧でコードに追加することを好みます。 各脚注には、ファイルに表示される順序で番号[ 1 ]が割り当てられ、文書の最後にきちんとグループ化されます。

残念ながら、正規表現でメモを取り出すことはできません。たとえば、 PREセクションに角かっこだけを入れることができます。 したがって、角括弧の「キャプチャ」は、トランスフォーマーを使用してファイルを(文字で)トラバースするプロセスでのみ実行されます。 はい、はい、これは美しいダッシュと引用符を作成するのと同じトランスフォーマーです。

写真としてのテキスト


ある種のフォントのデモンストレーションが必要な場合もあれば、検索エンジンによってインデックスに登録されないものを作成したい場合もあれば、コンテンツを盗作から保護したい場合もあります。 したがって、テキストをグラフィックとして表示できると便利です。 また、ClearTypeとOpenTypeを使用して出力できることは、通常、非常に優れています。 したがって、これらの目的のために、新しいMicrosoft 2次元グラフィックスAPIであるDirect2Dを使用して美しいテキストを描画する「アンマネージド」パーツをマネージドアプリケーション(WPF)に追加する必要がありました。 どのように機能しますか? さて、たとえば[{Hello, World!}]書いてい[{Hello, World!}]システムはこれで応答します:



プレーンテキストが機能した後、OpenTypeのフォント、サイズ、機能の実際のサポートを追加して、すべてがより美しく描画されるようにしました。



テキストをグラフィックスで免責する機会があるので、このプロセスを自動化して、たとえば、すべての見出しを自分の制作のグラフィックスで置き換えることができるようにしました。

最後に書いた貴重なスニペットの1つは、テキストが描画された後のビットマップの有用なサイズを測定する方法です。 私はこれをGDI +で行っていましたが、最近怒ってC ++で書いています:

MYAPI RECT MeasureCropArea( BYTE * src, int width, int height, int stride, int color)<br/>
{<br/>
Pixel& bgr = * reinterpret_cast <Pixel*>(&color);<br/>
RECT result;<br/>
// find the first non-conforming row of pixels
for ( int y = 0; y < height; ++y) <br/>
{<br/>
int y_offset = y * stride;<br/>
for ( int x = 0; x < width; ++x)<br/>
{<br/>
int offset = x * sizeof (Pixel) + y_offset;<br/>
Pixel& s = * reinterpret_cast <Pixel*>(src + offset);<br/>
// if this pixel is non-conforming, so is the row
if (s != bgr)<br/>
{<br/>
result.top = y;<br/>
// cause soft eject
x = width;<br/>
y = height;<br/>
}<br/>
}<br/>
}<br/>
// find the last non-conforming row of pixels
for ( int y = height - 1; y >= 0; --y) <br/>
{<br/>
int y_offset = y * stride;<br/>
for ( int x = 0; x < width; ++x)<br/>
{<br/>
int offset = x * sizeof (Pixel) + y_offset;<br/>
Pixel& s = * reinterpret_cast <Pixel*>(src + offset);<br/>
// if this pixel is non-conforming, so is the row
if (s != bgr)<br/>
{<br/>
result.bottom = y;<br/>
// cause soft eject
x = width;<br/>
y = -1;<br/>
}<br/>
}<br/>
}<br/>
// find the first non-conforming column of pixels
for ( int x = 0; x < width; ++x)<br/>
{<br/>
for ( int y = 0; y < height; ++y) <br/>
{<br/>
int offset = x * sizeof (Pixel) + y * stride;<br/>
Pixel& s = * reinterpret_cast <Pixel*>(src + offset);<br/>
// if this pixel is non-conforming, so is the column
if (s != bgr)<br/>
{<br/>
result.left = x;<br/>
// cause soft eject
x = width;<br/>
y = height;<br/>
}<br/>
}<br/>
}<br/>
// find the last non-conforming column of pixels
for ( int x = width - 1; x >= 0; --x)<br/>
{<br/>
for ( int y = 0; y < height; ++y) <br/>
{<br/>
int offset = x * sizeof (Pixel) + y * stride;<br/>
Pixel& s = * reinterpret_cast <Pixel*>(src + offset);<br/>
// if this pixel is non-conforming, so is the column
if (s != bgr)<br/>
{<br/>
result.right = x;<br/>
// cause soft eject
x = -1;<br/>
y = height;<br/>
}<br/>
}<br/>
}<br/>
int w = result.right - result.left;<br/>
int h = result.bottom - result.top;<br/>
if (w < 1) { result.left = 0; result.right = 1; }<br/>
if (h < 1) { result.top = 0; result.bottom = 1; }<br/>
return result;<br/>
}<br/>

構文の強調表示


RANT:Habrの記事見てうんざりしているところです。コードの後に​​、 This code was highlighted with CodeSyntaxHighlighter 。 これは不十分です。

エディターにもまったく同じ機能が必要でした。 その結果、すべての人をスパムした蛍光ペンと同じライブラリを使用し(そこにどのようにあったか正確には覚えていません)、少しAPIを飲んで成功しました- {{ }}を書くとハイライトが機能します。 コード例はこの投稿にあります:)

カウント


カウントは私の新しい趣味です。 アイデアを異なる方法で説明することが不可能な場合があります。 繰り返しますが、グラフグラフの自動生成は基本的にシンプルですが、実際には、シンプルなコマンドを記述し、出力にグラフを描画できるDSLの作成につながりました。 そのようなスペックから

edge basic advanced<br/>
edge advanced TOC`generation<br/>
edge advanced Text- as -image`generation<br/>
edge advanced Graph`generation<br/>
edge Heading`substitution<br/>
edge TOC`generation Heading`substitution<br/>
edge Heading`substitution Migration`from`FlowDocument`to`Direct2D<br/>
edge Text- as -image`generation Heading`substitution<br/>
edge Graph`generation Subpixel`hinting`(future)<br/>

そのような写真が判明します:




これはもちろんDSLであり、リフレクションを通じて実装しました。 各edgeディレクティブは、edgeメソッドの実際の呼び出しに変わります。 これらのコマンドのパーサーは次のようになります。

public void AddInstruction( string line)<br/>
{<br/>
if ( string .IsNullOrEmpty(line)) return ;<br/>
foreach (Match m in Regex.Matches(line, "([\"'])(?:\\\\\\1|.)*?\\1" ))<br/>
line = line.Replace(m.Value, m.Value.Replace( ' ' , '`' ));<br/>
string [] parts = line.Split( ' ' ).Select(p => p.Replace( '`' , ' ' ).Unquote()).ToArray();<br/>
if (parts.Length < 1) return ;<br/>
// having acquired the parts, see if there's a matching method
MethodInfo mi = null ;<br/>
if (cachedMethodInfo.ContainsKey( new KeyValuePair< string , int >(parts[0], parts.Length - 1)))<br/>
mi = cachedMethodInfo[ new KeyValuePair< string , int >(parts[0], parts.Length - 1)];<br/>
else <br/>
{<br/>
var methods = GetType().GetMethods().Where(m => m.Name.ToLower() == parts[0]<br/>
&& m.GetParameters().Length == (parts.Length - 1));<br/>
if (methods.Any())<br/>
{<br/>
mi = methods.First();<br/>
cachedMethodInfo.Add( new KeyValuePair< string , int >(mi.Name, parts.Length - 1), mi);<br/>
}<br/>
}<br/>
if (mi != null )<br/>
{<br/>
var pars = mi.GetParameters();<br/>
object [] ps = new object [0];<br/>
if (pars.Length == parts.Length - 1)<br/>
{<br/>
// try building a parameter array
ps = new object [pars.Length];<br/>
for ( int i = 0; i < pars.Length; i++)<br/>
{<br/>
var par = pars[i];<br/>
var source = parts[i + 1];<br/>
ps[i] = ConvertString(source, par.ParameterType);<br/>
}<br/>
}<br/>
// parameters ready - call it
try { mi.Invoke( this , ps); }<br/>
catch (Exception ex) { }<br/>
}<br/>
}<br/>

上記のコードの一部では、メソッドに関する情報の高価な検索をキャッシュしようとしました。 おそらくより良い解決策がありますが、それは私にとってはうまくいきます。 これは、小さなDSLインタープリターをすぐに取得するために、F#を使用したり、DLRに登る必要は必ずしもないことを示しています。

話したかったのはそれだけです。

注釈


  1. この脚注の番号は1です

Source: https://habr.com/ru/post/J72527/


All Articles