キャンバスにおけるトークン化マークダウンと描画コードブロック


“”で私の記事を読む場合は、キャンバスレンダリングエディタにいくつかのテキストと見出しを書くための基本的な方法を今する必要があります.この記事では、コードAPIを埋め込むサポートを追加するために、キャンバスAPIを使用して作業を継続します.いくつかのカスタム図形をレンダリングし、レンダリングの複数の型をサポートするために私たちのコードをリファクタにいくつかのより多くのキャンバス機能を使用します.

キャンバスの描画図形


キャンバスの描画図形は、APIに関する限り、非常に簡単です.単純に描画する方法を調整するには、既存のキャンバスのレンダリングコンテキストを使用して、描画するものとそれに従ってください.あなたの絵筆としてコンテキスト上のさまざまなプロパティを考えてください.
長方形を描きたいとしましょう.これを行うにはレンダリングコンテキストを取得し、fillRect and fillStyle 呼び出し.
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

context.fillStyle = 'rgb(200, 0, 0)';
context.fillRect(10, 10, 50, 50);

context.fillStyle = 'rgba(0, 0, 200, 0.5)';
context.fillRect(30, 30, 50, 50);

対照的に、我々が長方形のちょうど端を描くことを望むならば、我々は対応するメソッドを使うことができますstrokeRect and strokeStyle .
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

context.strokeStyle = 'green';
context.strokeRect(20, 10, 160, 100);

キャンバスの描画APIの残りの部分は、通常、パスと円弧で動作します.例えば、円を描くにはarcbeginPath どちらともfill or stroke .
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

context.strokeStyle = 'green';
context.beginPath();
context.arc(100, 75, 50, 0, 2 * Math.PI);
context.stroke();

アークに加えて、我々もellipse メソッド:

The ellipse() method creates an elliptical arc centered at (x, y) with the radii radiusX and radiusY. The path starts at startAngle and ends at endAngle, and travels in the direction given by anticlockwise (defaulting to clockwise).


Markdownのコードスニペットを解析する


私たちのMarkdownテキストが見出しのような他のものを含んでいるなら、我々はコード断片に遭遇するとき、見つける方法を必要とします.標準の3つのbackticksを使用します.このテキストを解析するために少しの断片を書きましょう.
function parse(lines) {
    let cur = [];
    let tokens = [];
    for (let i = 0; i < lines.length; i++) {
        let line = lines[i];
        let matches = line.match(/^`{3}([a-zA-Z]*)/);
        if (matches) {
           let type = matches[1];
           if (cur.length && cur[0].code) {
               type = cur[0].type;
               tokens.push({ code: cur.slice(1), type });
               cur = [];
           } else {
               cur.push({ line, code: true, type });
           }
           continue;
        } else if (!cur.length && line.match(/^\s*\#/g)) {
            let level = line.match(/^\s*\#/g).length;
            tokens.push({ heading: line, level });
            continue;
        }
        if (!cur.length) {
            tokens.push(line);
        } else {
            cur.push(line);
        }
    }
    if (cur.length) {
        tokens.push(cur[0].line, ...cur.slice(1));
    }
    return tokens;
}
上の我々のスニペットでは、それぞれの行を通過し、コードブロックと一致するかどうかを確認します.次に、現在のトークンの状態に応じて、現在のトークンを追加し、見出しをパースしたり、コードブロックが完了するまで、現在まで追加します.
以下のサンプルを出力するには、いくつかのテキストを解析します.
[
  { heading: '# hello', level: 1 },
  '',
  '',
  { code: [ 'A->B', 'B->C', 'B->D' ], type: 'graph' },
  '',
  { heading: '## bleh!', level: 2 },
  '',
  'hi'
]

ヘッダーとコードのトークンのレンダリング


先に行き、前の描画コードを更新し、物事を交換しましょう.我々は、利用するつもりですtextAlign レンダリングコンテキストでは、我々はまだテキストを測定することを心配する必要はありません.
function draw() {
    context.clearRect(0, 0, window.innerWidth, window.innerHeight);

    let offset = 100;
    let tokens = parse(text);
    tokens.forEach(token => {
        if (token.code) {
            offset += renderCode(token, offset);
        } else {
            offset += renderText(token, offset);
        }
    });
}

function renderCode(token, offset) {
    let height = 0;
    token.code.forEach(c => {
        let h = renderText(c, offset);
        height += h;
        offset += h;
    });
    return height;
}

function renderText(token, offset) {
    let lineHeight = 1.5;
    let headingSize = 32;
    let baseSize = 16;
    let height = baseSize * lineHeight;
    if (token.heading) {
        let size = headingSize - (token.level * 4);
        context.font = `bold ${size}px roboto`;
        height = size * lineHeight;
    } else {
        context.font = `${baseSize}px roboto`;
    }

    context.textAlign = 'center';
    context.fillText(token, window.innerWidth / 2, offset);
    return height;
}

レンダリングテキストは、前の記事の前とほとんど同じです、そして、現在、私は単にコードを規則的なテキストとして描いています.どのように我々はコードにバックスペースをすることができますし、我々が働いていたものを再編集するにも注意してください!これは、入力が生のテキストで作業中にレンダリングコードがトークンで動作しているためです.かなりきれい!

コードブロックの描画


コードのブロックのようなものを実際にレンダリングするためにレンダルコードブロックを修正してこの記事を完成しましょう.以下に必要なことがいくつかあります.
  • 測定ブロックに基づくコードブロックの最大幅を見つける
  • 線の数、フォントサイズと線高さに基づいてコード・ブロックの高さを計算してください
  • 実際の長方形を描画する
  • 初期オフセット調整
  • コードの行を描画する
  • ブロック後のオフセットを調整する
  • function renderCode(token, offset) {
        let height = 0;
        context.font = '16px roboto';
    
        let lens = token.code.map(c => c.length);
        let maxLen = Math.max(...lens);
        let maxText = token.code.find(c => c.length === maxLen);
        let maxWidth = Math.max(context.measureText(maxText).width, 300);
        let x = window.innerWidth / 2 - maxWidth / 2;
        let maxHeight = token.code.length * 16 * 1.5;
        context.fillStyle = '#cccccc';
        context.lineWidth = 3;
        context.strokeRect(x, offset, maxWidth, maxHeight);
        context.fillRect(x, offset, maxWidth, maxHeight);
    
        // before
        offset += 16;
        height += 16;
    
        token.code.forEach(c => {
            let h = renderText(c, offset);
            height += h;
            offset += h;
        });
    
        // after
        offset += 16;
        height += 16;
    
        return height;
    }
    

    それだ!

    結論


    私たちがコードブロックをフォーマットする段階に達していない間、私たちは少しの時制化を管理しました、そして、我々はもう少しでキャンバスAPIについて学びました.私が最初に書いたとき、グラフツリーをどのようにレンダリングするかを示したかった.残念ながら、木のためのレイアウトアルゴリズムは、より少しの深さです😄 パン!)とツリー横断アルゴリズムのいくつかの背景が必要です.このシリーズの私の次の記事では、Markdownから実際のグラフをレンダリングするためのセットアップとして、ツリーの横断とレイアウトのアルゴリズムを使います.
    ステイ!📺 👨🏻‍💻
    あなたがこの記事が好きならば、私にフォローなどを与えてください.また、私は私の最新の更新と混合コンテンツを投稿私の場所をチェックアウトしてください.
    再びありがとう!
    乾杯!🍻