마크다운 토큰화 및 캔버스에서 코드 블록 그리기

""에 대한 내 게시물을 읽으면 이제 캔버스 렌더링 편집기에 일부 텍스트와 제목을 작성하는 기본 방법을 갖게 될 것입니다. 이 게시물에서는 코드 블록 포함에 대한 지원을 추가하기 위해 캔버스 API 작업을 계속할 것입니다. 몇 가지 더 많은 캔버스 함수를 사용하여 일부 사용자 정의 모양을 렌더링하고 여러 유형의 렌더링을 지원하도록 코드를 리팩터링합니다.

캔버스에 도형 그리기



캔버스에 도형을 그리는 것은 API에 관한 한 매우 간단합니다. 기존 캔버스 렌더링 컨텍스트를 사용하여 그리려는 방법을 조정하고 그리려는 내용을 따르기만 하면 됩니다. 컨텍스트의 다양한 속성을 페인트 브러시로 생각하십시오.

직사각형을 그리고 싶다고 가정해 봅시다. 이를 위해 렌더링 컨텍스트를 얻고 fillRectfillStyle 호출을 호출합니다.

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);



반대로 직사각형의 가장자리만 그리려면 해당 메서드 strokeRectstrokeStyle 을 사용할 수 있습니다.

const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

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



나머지 캔버스 그리기 API는 일반적으로 경로와 호에서 작동합니다. 예를 들어 원을 그리려면 arcbeginPathfill 또는 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();



arc 외에도 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).



마크다운에서 코드 스니펫 파싱



마크다운 텍스트에 제목과 같은 다른 항목이 포함되어 있다는 점을 감안할 때 코드 스니펫을 만났을 때 이를 알아낼 방법이 필요합니다. 표준 세 개의 백틱을 사용합니다. 이 텍스트를 구문 분석하기 위해 작은 스니펫을 작성해 보겠습니다.

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;
}



렌더링 텍스트는 이전 기사의 이전 기사와 거의 동일하며 이제 코드를 일반 텍스트로 렌더링합니다. 코드로 백스페이스를 이동하고 작업 중인 내용을 다시 편집하는 방법도 주목하십시오! 이는 입력이 원시 텍스트로 작업하는 동안 렌더링 코드가 토큰으로 작업하기 때문입니다. 꽤 깔끔한!

코드 블록 그리기



코드 블록처럼 보이는 것을 실제로 렌더링하도록 renderCode 블록을 수정하여 이 기사를 마무리하겠습니다. 아래에서 수행해야 할 몇 가지 작업이 있습니다.
  • measureText를 기반으로 코드 블록의 최대 너비 찾기
  • 줄 수, 글꼴 크기 및 줄 높이를 기준으로 코드 블록의 높이를 계산합니다.
  • 실제 직사각형 렌더링
  • 초기 오프셋 조정
  • 코드 줄을 렌더링합니다.
  • 블록 후 오프셋 조정

  • 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에 대해 조금 더 배웠습니다. 처음에 이것을 작성했을 때 그래프 트리를 렌더링하는 방법을 보여주고 싶었습니다. 불행하게도, 트리에 대한 레이아웃 알고리즘은 좀 더 깊이 있고(😄 말장난!) 트리 순회 알고리즘에 대한 약간의 배경 지식이 필요합니다. 이 시리즈의 다음 기사에서는 마크다운에서 실제 그래프를 렌더링하기 위한 설정으로 트리 순회 및 레이아웃 알고리즘을 살펴보겠습니다.

    계속 지켜봐! 📺 👨🏻‍💻


    이 글이 마음에 드셨다면 팔로우와 좋아요 부탁드립니다. 또한 최신 업데이트 및 혼합 콘텐츠를 게시하는 위치를 자유롭게 확인하십시오.

    다시 한번 감사합니다!

    건배! 🍻

    좋은 웹페이지 즐겨찾기