commit 313909c1fa097467683a4af86658de6bfa26597c
parent 3a797aee88896c4f71a062c9281801f25821246e
Author: Jake Bauer <jbauer@paritybit.ca>
Date: Sat, 18 Feb 2023 16:49:44 -0500
Implement chapter 3
Diffstat:
M | browser.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__":