p, pre, br, b, img, separator, i, u, small, mark, code tags

This commit is contained in:
Face
2025-07-22 15:34:42 +03:00
parent b3526927cb
commit 1f73a7070f
82 changed files with 841 additions and 48 deletions

View File

@@ -23,6 +23,18 @@ class HTMLElement:
func get_id() -> String:
return get_attribute("id")
func get_collapsed_text() -> String:
# Collapse whitespace: replace multiple spaces, tabs, newlines with single space
var collapsed = text_content.strip_edges()
# Replace multiple whitespace characters with single space
var regex = RegEx.new()
regex.compile("\\s+")
return regex.sub(collapsed, " ", true)
func get_preserved_text() -> String:
# For pre tags - preserve all whitespace
return text_content
class ParseResult:
var root: HTMLElement
@@ -149,6 +161,10 @@ func get_title() -> String:
var title_element = find_first("title")
return title_element.text_content if title_element != null else ""
func get_icon() -> String:
var icon_element = find_first("icon")
return icon_element.get_attribute("src")
func get_meta_content(name_: String) -> String:
var meta_elements = find_all("meta", "name")
for element in meta_elements:

61
Scripts/Network.gd Normal file
View File

@@ -0,0 +1,61 @@
extends Node
func fetch_image(url: String) -> ImageTexture:
var http_request = HTTPRequest.new()
add_child(http_request)
var error = http_request.request(url)
if error != OK:
print("Error making HTTP request: ", error)
http_request.queue_free()
return null
var response = await http_request.request_completed
var result = response[0] # HTTPClient.Result
var response_code = response[1] # int
var headers = response[2] # PackedStringArray
var body = response[3] # PackedByteArray
http_request.queue_free()
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
print("Failed to fetch image. Result: ", result, " Response code: ", response_code)
return null
# Get content type from headers
var content_type = ""
for header in headers:
if header.to_lower().begins_with("content-type:"):
content_type = header.split(":")[1].strip_edges().to_lower()
break
var image: Image = Image.new()
var load_error
# Load image based on content type
if content_type.contains("png") or url.to_lower().ends_with(".png"):
load_error = image.load_png_from_buffer(body)
elif content_type.contains("jpeg") or content_type.contains("jpg") or url.to_lower().ends_with(".jpg") or url.to_lower().ends_with(".jpeg"):
load_error = image.load_jpg_from_buffer(body)
elif content_type.contains("webp") or url.to_lower().ends_with(".webp"):
load_error = image.load_webp_from_buffer(body)
elif content_type.contains("bmp"):
load_error = image.load_bmp_from_buffer(body)
elif content_type.contains("tga"):
load_error = image.load_tga_from_buffer(body)
else:
print("Unknown or missing content-type. Attempting bruteforce converting across PNG, JPG and WebP...")
load_error = image.load_png_from_buffer(body)
if load_error != OK:
load_error = image.load_jpg_from_buffer(body)
if load_error != OK:
load_error = image.load_webp_from_buffer(body)
if load_error != OK:
print("Failed to load image from buffer. Content-Type: ", content_type, " Error: ", load_error)
return null
var texture = ImageTexture.create_from_image(image)
return texture

1
Scripts/Network.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bbfpng3opsnyp

View File

@@ -7,6 +7,7 @@ signal tab_closed
@onready var gradient_texture: TextureRect = $GradientTexture
@onready var button: Button = $Button
@onready var close_button: Button = $CloseButton
@onready var icon: TextureRect = $Icon
const TAB_GRADIENT: GradientTexture2D = preload("res://Scenes/Styles/TabGradient.tres")
const TAB_GRADIENT_DEFAULT: GradientTexture2D = preload("res://Scenes/Styles/TabGradientDefault.tres")
@@ -36,6 +37,13 @@ func _process(_delta):
else:
close_button.add_theme_stylebox_override("normal", CLOSE_BUTTON_NORMAL)
func set_title(title: String) -> void:
button.text = title
func set_icon(new_icon: Texture) -> void:
icon.texture = new_icon
icon.rotation = 0
func _on_button_mouse_entered() -> void:
mouse_over_tab = true
if is_active: return

View File

@@ -1,8 +1,11 @@
class_name TabManager
extends HFlowContainer
var tabs: Array[Tab] = []
var active_tab := 0
@onready var main: Main = $"../.."
const TAB = preload("res://Scenes/Tab.tscn")
const TAB_NORMAL: StyleBoxFlat = preload("res://Scenes/Styles/TabNormal.tres")
@@ -79,6 +82,9 @@ func create_tab() -> void:
h_box_container.add_child(tab)
set_active_tab(index)
# WARNING: temporary
main.render()
func _input(event: InputEvent) -> void:
if Input.is_action_just_pressed("NewTab"):

5
Scripts/Tags/bold.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24][b]%s[/b][/font_size]" % element.get_collapsed_text()

1
Scripts/Tags/bold.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://chdftsciuecfk

4
Scripts/Tags/br.gd Normal file
View File

@@ -0,0 +1,4 @@
extends Control
func init(element: HTMLParser.HTMLElement) -> void:
custom_minimum_size.y = 24

1
Scripts/Tags/br.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://csd2kcqixac65

5
Scripts/Tags/code.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=20][code]%s[/code][/font_size]" % element.get_collapsed_text()

1
Scripts/Tags/code.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://c3dnmqsnj0akr

11
Scripts/Tags/img.gd Normal file
View File

@@ -0,0 +1,11 @@
extends TextureRect
func init(element: HTMLParser.HTMLElement) -> void:
var src = element.get_attribute("src")
if !src: return print("Ignoring <img/> tag without \"src\" attribute.")
texture = await Network.fetch_image(src)
var texture_size = texture.get_size()
custom_minimum_size = texture_size
size = texture_size

1
Scripts/Tags/img.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://dgakysfyq773t

5
Scripts/Tags/italic.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24][i]%s[/i][/font_size]" % element.get_collapsed_text()

View File

@@ -0,0 +1 @@
uid://bamty87whxwxi

5
Scripts/Tags/mark.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24][bgcolor=#FFFF00]%s[/bgcolor][/font_size]" % element.get_collapsed_text()

1
Scripts/Tags/mark.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://c2i8dwyyuufff

5
Scripts/Tags/p.gd Normal file
View File

@@ -0,0 +1,5 @@
extends Control
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24]%s[/font_size]" % element.get_collapsed_text()

1
Scripts/Tags/p.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cg6kjvlx3an1j

5
Scripts/Tags/pre.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=20][code]%s[/code][/font_size]" % element.get_preserved_text()

1
Scripts/Tags/pre.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://8mam5rvtx72x

26
Scripts/Tags/separator.gd Normal file
View File

@@ -0,0 +1,26 @@
extends Control
var separator_node: Separator
func init(element: HTMLParser.HTMLElement) -> void:
var direction = element.get_attribute("direction")
if direction == "vertical":
separator_node = VSeparator.new()
separator_node.size_flags_vertical = Control.SIZE_EXPAND_FILL
separator_node.custom_minimum_size.x = 2
separator_node.layout_mode = 1
separator_node.anchors_preset = Control.PRESET_LEFT_WIDE
else:
separator_node = HSeparator.new()
separator_node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
separator_node.custom_minimum_size.y = 2
separator_node.layout_mode = 1
separator_node.anchors_preset = Control.PRESET_FULL_RECT
add_child(separator_node)
# Make the parent control also expand to fill available space
size_flags_horizontal = Control.SIZE_EXPAND_FILL
if direction == "vertical":
size_flags_vertical = Control.SIZE_EXPAND_FILL

View File

@@ -0,0 +1 @@
uid://rol353cupbbf

5
Scripts/Tags/small.gd Normal file
View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=18]%s[/font_size]" % element.get_collapsed_text()

View File

@@ -0,0 +1 @@
uid://xr7n1lat501x

View File

@@ -0,0 +1,5 @@
extends VBoxContainer
func init(element: HTMLParser.HTMLElement) -> void:
var label: RichTextLabel = $RichTextLabel
label.text = "[font_size=24][u]%s[/u][/font_size]" % element.get_collapsed_text()

View File

@@ -0,0 +1 @@
uid://x0hphtm8kf12

View File

@@ -1,12 +1,32 @@
class_name Main
extends Control
func _ready():
render()
@onready var website_container: Control = %WebsiteContainer
@onready var tab_container: TabManager = $VBoxContainer/TabContainer
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
var loading_tween: Tween
const P = preload("res://Scenes/Tags/p.tscn")
const IMG = preload("res://Scenes/Tags/img.tscn")
const SEPARATOR = preload("res://Scenes/Tags/separator.tscn")
const BOLD = preload("res://Scenes/Tags/bold.tscn")
const ITALIC = preload("res://Scenes/Tags/italic.tscn")
const UNDERLINE = preload("res://Scenes/Tags/underline.tscn")
const SMALL = preload("res://Scenes/Tags/small.tscn")
const MARK = preload("res://Scenes/Tags/mark.tscn")
const CODE = preload("res://Scenes/Tags/code.tscn")
const PRE = preload("res://Scenes/Tags/pre.tscn")
const BR = preload("res://Scenes/Tags/br.tscn")
func render():
# Clear existing content
for child in website_container.get_children():
child.queue_free()
var html_bytes = "<head>
<title>My cool web</title>
<icon href=\"https://buss.log/icon.ico\"> <!--This image will be the page's icon-->
<icon src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Google_%22G%22_logo.svg/768px-Google_%22G%22_logo.svg.png\"> <!--This image will be the page's icon-->
<meta name=\"theme-color\" content=\"#000000\">
<meta name=\"description\" content=\"My cool web\">
@@ -16,14 +36,29 @@ func render():
</head>
<body>
<h1>Hey there!</h1>
<img href=\"https://buss.log/rick-astley.png\" />
<p>Hey there! this is a test</p>
<b>This is bold</b>
<i>This is italic</i>
<u>This is underline</u>
<small>this is small</small>
<mark>this is marked</mark>
<code>this is code</code>
<pre>
Text in a pre element
is displayed in a fixed-width
font, and it preserves
both spaces and
line breaks
</pre>
<script src=\"script2.lua\" />
<separator direction=\"horizontal\" />
<img src=\"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQMNUPIKabszX0Js_c0kfa4cz_JQYKfGTuBUA&s\" />
<separator direction=\"vertical\" />
</body>".to_utf8_buffer()
# Create parser and parse
var parser = HTMLParser.new(html_bytes)
var parser: HTMLParser = HTMLParser.new(html_bytes)
var parse_result = parser.parse()
print("Total elements found: " + str(parse_result.all_elements.size()))
@@ -32,3 +67,77 @@ func render():
print("Parse errors: " + str(parse_result.errors))
# TODO: render the shit on the screen
var tab = tab_container.tabs[tab_container.active_tab]
var title = parser.get_title()
tab.set_title(title)
var icon = parser.get_icon()
set_loading_icon(tab)
tab.set_icon(await Network.fetch_image(icon))
stop_loading_icon()
var body = parser.find_first("body")
for element: HTMLParser.HTMLElement in body.children:
match element.tag_name:
"p":
var p = P.instantiate()
p.init(element)
website_container.add_child(p)
"pre":
var pre = PRE.instantiate()
pre.init(element)
website_container.add_child(pre)
"br":
var br = BR.instantiate()
br.init(element)
website_container.add_child(br)
"b":
var bold = BOLD.instantiate()
bold.init(element)
website_container.add_child(bold)
"img":
var img = IMG.instantiate()
img.init(element)
website_container.add_child(img)
"separator":
var separator = SEPARATOR.instantiate()
separator.init(element)
website_container.add_child(separator)
"i":
var italic = ITALIC.instantiate()
italic.init(element)
website_container.add_child(italic)
"u":
var underline = UNDERLINE.instantiate()
underline.init(element)
website_container.add_child(underline)
"small":
var small = SMALL.instantiate()
small.init(element)
website_container.add_child(small)
"mark":
var mark = MARK.instantiate()
mark.init(element)
website_container.add_child(mark)
"code":
var code = CODE.instantiate()
code.init(element)
website_container.add_child(code)
_:
print("Couldn't parse unsupported HTML tag \"%s\"" % element.tag_name)
func set_loading_icon(tab: Tab) -> void:
tab.set_icon(LOADER_CIRCLE)
loading_tween = create_tween()
loading_tween.set_loops()
var icon = tab.icon
icon.pivot_offset = Vector2(11.5, 11.5)
loading_tween.tween_method(func(angle): icon.rotation = angle, 0.0, TAU, 1.0)
func stop_loading_icon() -> void:
if loading_tween:
loading_tween.kill()
loading_tween = null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dioe63fr47oq7"
path="res://.godot/imported/youtube.png-5feacc6342e9a438cbbd6a63702ea1ce.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Scripts/youtube.png"
dest_files=["res://.godot/imported/youtube.png-5feacc6342e9a438cbbd6a63702ea1ce.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1