browser.py

Working through the exercises at browser.engineering
git clone https://git.sr.ht/~jbauer/browser.py
Log | Files | Refs | README | LICENSE

commit 313909c1fa097467683a4af86658de6bfa26597c
parent 3a797aee88896c4f71a062c9281801f25821246e
Author: Jake Bauer <jbauer@paritybit.ca>
Date:   Sat, 18 Feb 2023 16:49:44 -0500

Implement chapter 3

Diffstat:
Mbrowser.py | 150++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 125 insertions(+), 25 deletions(-)

diff --git a/browser.py b/browser.py @@ -1,7 +1,10 @@ import tkinter +import tkinter.font import socket import ssl +FONTS = {} + WIDTH, HEIGHT = 800, 600 HSTEP, VSTEP = 13, 18 SCROLLSTEP = 100 @@ -11,12 +14,14 @@ class Browser: self.scroll = 0 self.window = tkinter.Tk() self.canvas = tkinter.Canvas( - self.window, - width=WIDTH, - height=HEIGHT + self.window, + width=WIDTH, + height=HEIGHT, ) self.canvas.pack() self.window.bind("<Down>", self.scrolldown) + self.window.bind("<Up>", self.scrollup) + self.display_list = [] def scrolldown(self, e): @@ -25,31 +30,111 @@ class Browser: self.draw() + def scrollup(self, e): + self.canvas.delete("all") + self.scroll -= SCROLLSTEP + self.draw() + + def load(self, url): + print("Making request to", url) headers, body = request(url) - text = lex(body) - self.display_list = layout(text) + print("Received response.") + print("Lexing response body...") + tokens = lex(body) + print("Laying out page...") + self.display_list = Layout(tokens).display_list + print("Drawing canvas...") self.draw() + print("Done.") def draw(self): - for x, y, c in self.display_list: + for x, y, word, font 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) - - -def layout(text): - display_list = [] - cursor_x, cursor_y = HSTEP, VSTEP - for c in text: - display_list.append((cursor_x, cursor_y, c)) - if cursor_x >= WIDTH - HSTEP: - cursor_y += VSTEP - cursor_x = HSTEP - else: - cursor_x += HSTEP - return display_list; + self.canvas.create_text(x, y - self.scroll, text=word, + font=font, anchor="nw") + + +class Layout: + def __init__(self, tokens): + self.display_list = [] + self.line = [] + self.cursor_x = HSTEP + self.cursor_y = VSTEP + self.weight = "normal" + self.slant = "roman" + self.size = 10 + self.display = True + for tok in tokens: + self.token(tok) + self.flush() + + + def flush(self): + if not self.line: return + metrics = [font.metrics() for x, word, font in self.line] + max_ascent = max([metric["ascent"] for metric in metrics]) + baseline = self.cursor_y + 1.25 * max_ascent + for x, word, font in self.line: + y = baseline - font.metrics("ascent") + self.display_list.append((x, y, word, font)) + self.cursor_x = HSTEP + self.line = [] + max_descent = max([metric["descent"] for metric in metrics]) + self.cursor_y = baseline + 1.25 * max_descent + + def text(self, tok): + font = get_font(self.size, self.weight, self.slant) + for word in tok.text.split(): + w = font.measure(word) + if self.cursor_x + w >= WIDTH - HSTEP: + self.flush() + self.line.append((self.cursor_x, word, font)) + self.cursor_x += w + font.measure(" ") + + + def token(self, tok): + if isinstance(tok, Text): + if self.display: + self.text(tok) + elif tok.tag == "i" or tok.tag == "em": + self.slant = "italic" + elif tok.tag == "/i" or tok.tag == "/em": + self.slant = "roman" + elif tok.tag == "b" or tok.tag == "strong": + self.weight = "bold" + elif tok.tag == "/b" or tok.tag == "/strong": + self.weight = "normal" + elif tok.tag == "small": + self.size -= 2 + elif tok.tag == "/small": + self.size += 2 + elif tok.tag == "big": + self.size += 4 + elif tok.tag == "/big": + self.size -= 4 + elif tok.tag == "style": + self.display = False + elif tok.tag == "/style": + self.display = True + elif tok.tag == "br": + self.flush() + elif tok.tag == "/p": + self.flush() + self.cursor_y += VSTEP + + + +class Text: + def __init__(self, text): + self.text = text + + +class Tag: + def __init__(self, tag): + self.tag = tag def request(url): @@ -103,16 +188,31 @@ def request(url): def lex(body): + out = [] text = "" - in_angle = False + in_tag = False for c in body: if c == "<": - in_angle = True + in_tag = True + if text: out.append(Text(text)) + text = "" elif c == ">": - in_angle = False - elif not in_angle: + in_tag = False + out.append(Tag(text)) + text = "" + else: text += c - return text + if not in_tag and text: + out.append(Text(text)) + return out + + +def get_font(size, weight, slant): + key = (size, weight, slant) + if key not in FONTS: + font = tkinter.font.Font(size=size, weight=weight, slant=slant) + FONTS[key] = font + return FONTS[key] if __name__ == "__main__":