Ch2 화면에 그리기

Feb 19th, 2026

 Timothy @ Daangn Frontend Core

Software Engineer, Frontend @Daangn Frontend Core Team

ex) Senior Lead Software Engineer @NHN Dooray (2021 ~ 2023)

ex) Lead Software Engineer @ProtoPie (2016 ~ 2021)

ex) Microsoft MVP

Timothy Lee

이 웅재

2.1 창 만들기

프로그램과 데스크톱 환경

  • 프로그램이 새 창을 요청하면 데스크톱 환경이 실제로 새 창을 표시합니다.

  • 프로그램이 해당 창에 특정 요소를 그리면 데스크톱 환경이 이를 화면에 표시합니다.​

    • ​그래픽 툴킷을 사용하여, 간소화

    • 파이썬은 tkinter (Tcl 이라는 언어를 위해 작성된 Tk 라는 라이브러리의 인터페이스)

  • 데스크톱 환경은 클릭이나 키 입력을 프로그램에 알려주고, 프로그램은 이를 처리하고 창을 다시 그립니다.

tkinter.Tk()

  • 데스크톱 환경에 창을 만들도록 요청

  • 그 창에 무언가를 그리는데 사용할 객체를 반환 (window)

if __name__ == "__main__":
    import tkinter
    window = tkinter.Tk()
    tkinter.mainloop()

tkinter.mainloop()

  • pendingEvents 는 데스크톱 환경으로부터 마우스 클릭이나 키 입력 등 쌓여 있는 최근 이벤트를 얻고,

  • handleEvent 를 이용하여 애플리케이션의 상태를 업데이트 한 뒤에

  • drawScreen 을 통해 창을 다시 그린다.

# 이벤트 루프 의사 코드
while True
	for evt in pendingEvents():
    	handleEvent(evt)
    drawScreen()

tkinter.mainloop()

2.2 창에 그리기

tkinter.Canvas

  • TkWidget 중에 하나인 Canvas 에 브라우저를 그려요.

if __name__ == "__main__":
    import tkinter
    WIDTH, HEIGHT = 800, 600
    window = tkinter.Tk()
    canvas = tkinter.Canvas(window, width=WIDTH, height=HEIGHT)
    canvas.pack()
    tkinter.mainloop()

Class 로 변경

import tkinter

WIDTH, HEIGHT = 800, 600

class Browser:
    def __init__(self, canvas):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()

Canvas 에 도형 그리기

import tkinter

WIDTH, HEIGHT = 800, 600

class Browser:
    def __init__(self, canvas):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()
        
    def load(self, url):
        self.canvas.create_rectangle(10, 20, 400, 300)
        self.canvas.create_oval(100, 100, 150, 150)
        self.canvas.create_text(200, 150, text="Hello, World!")

2.3 텍스트 배치하기

"show" to "lex"

  • HTML 문서의 텍스트를 출력 대신 반환

def show(body):
    in_tag = False
    for c in body:
        if c == "<":
            in_tag = True
        elif c == ">":
            in_tag = False
        elif not in_tag:
            print(c, end="")
def lex(body):
    text = ""
    in_tag = False
    for c in body:
        if c == "<":
            in_tag = True
        elif c == ">":
            in_tag = False
        elif not in_tag:
            text += c
    return text

text 를 한글자씩 출력

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()

    def load(self, url):
        body = url.request()
        text = lex(body)
        for c in text:
            self.canvas.create_text(100, 100, text=c)

한글자씩 다른 자리에 출력

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()

    def load(self, url):
        HSTEP, VSTEP = 13, 18
        cursor_x, cursor_y = HSTEP, VSTEP
        body = url.request()
        text = lex(body)
        for c in text:
            self.canvas.create_text(
                cursor_x,
                cursor_y,
                text=c
            )
            cursor_x += HSTEP

한글자씩 다른 자리에 출력하면서 줄바꿈

    def load(self, url):
        HSTEP, VSTEP = 13, 18
        cursor_x, cursor_y = HSTEP, VSTEP
        body = url.request()
        text = lex(body)
        for c in text:
            self.canvas.create_text(
                cursor_x,
                cursor_y,
                text=c
            )
            cursor_x += HSTEP
            if cursor_x > WIDTH - HSTEP:
                cursor_y += VSTEP
                cursor_x = HSTEP

2.4 텍스트 스크롤하기

레이아웃

  • 페이지의 모든 요소가 어디에 있어야 하는지를 결정하는 것

  • 페이지 좌표계로 레이아웃을 결정하고, 스크린 좌표계로 페이지를 랜더링한다.

layout

HSTEP, VSTEP = 13, 18

def layout(text):
    display_list = []
    cursor_x, cursor_y = HSTEP, VSTEP
    for c in text:
        display_list.append(
            (cursor_x, cursor_y, c)
        )
        cursor_x += HSTEP
        if cursor_x > WIDTH - HSTEP:
            cursor_y += VSTEP
            cursor_x = HSTEP
    return display_list

display_list

WIDTH, HEIGHT = 800, 600

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()

    def draw(self):
        for x, y, c in self.display_list:
            self.canvas.create_text(x, y, text=c)

    def load(self, url):
        body = url.request()
        text = lex(body)
        self.display_list = layout(text)
        self.draw()

self.scroll

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()
        self.scroll = 0

y - self.scroll

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()
        self.scroll = 0

    def draw(self):
        for x, y, c in self.display_list:
            self.canvas.create_text(x, y - self.scroll, text=c)

아래 화살표 키 바인딩

WIDTH, HEIGHT = 800, 600
SCROLL_STEP = 10

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()
        self.scroll = 0
        self.window.bind("<Down>", self.scrolldown)

    def draw(self):
        for x, y, c in self.display_list:
            self.canvas.create_text(x, y - self.scroll, text=c)

    def scrolldown(self, e):
        self.scroll += SCROLL_STEP
        self.draw()

다시 그리기 전에 지우기

class Browser:
    def __init__(self):
        self.window = tkinter.Tk()
        self.canvas = tkinter.Canvas(self.window, width=WIDTH, height=HEIGHT)
        self.canvas.pack()
        self.scroll = 0
        self.window.bind("<Down>", self.scrolldown)

    def draw(self):
        for x, y, c in self.display_list:
            self.canvas.create_text(x, y - self.scroll, text=c)

    def scrolldown(self, e):
        self.scroll += SCROLL_STEP
        self.canvas.delete("all")
        self.draw()

2.5 더 빠른 랜더링

  • 부드럽다고 느껴지도록

  • frame rate 60 이상

  • 브라우저가 16ms 이내에 다시 그리기를 완료

  • 이러한 이유로 16ms 를 애니메이션 프레임 버짓 이라고 한다.

화면 밖에 있는 글자는 그리지 않는 방법

  • 첫번째: 창의 아래 글자를 그리지 않음

  • 두번째: 창의 위 글자를 그리지 않음

    def draw(self):
        for x, y, c in self.display_list:
            if y > self.scroll + HEIGHT:
                continue
            if y + VSTEP < self.scroll:
                continue
            self.canvas.create_text(x, y - self.scroll, text=c)

챕터2 화면에 그리기

By Woongjae Lee

챕터2 화면에 그리기

  • 36