From 65b393fbcb8e03c1136286a11b07f1788f005055 Mon Sep 17 00:00:00 2001
From: TEC
Date: Thu, 18 May 2023 19:07:57 +0800
Subject: [PATCH] Add text/html show method for styled strings
---
base/strings/io.jl | 173 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 173 insertions(+)
diff --git a/base/strings/io.jl b/base/strings/io.jl
index 43c7b8ab156c1..d9498620bdcdd 100644
--- a/base/strings/io.jl
+++ b/base/strings/io.jl
@@ -1025,3 +1025,176 @@ function show(io::IO, ::MIME"text/plain", c::StyledChar)
show(io, c.char)
end
end
+
+"""
+A mapping between ANSI named colors and 8-bit colors for use in HTML
+representations.
+"""
+const HTML_BASIC_COLORS = Dict{Symbol, SimpleColor}(
+ :black => SimpleColor(0x00, 0x00, 0x00),
+ :red => SimpleColor(0x80, 0x00, 0x00),
+ :green => SimpleColor(0x00, 0x80, 0x00),
+ :yellow => SimpleColor(0x80, 0x80, 0x00),
+ :blue => SimpleColor(0x00, 0x00, 0x80),
+ :magenta => SimpleColor(0x80, 0x00, 0x80),
+ :cyan => SimpleColor(0x00, 0x80, 0x80),
+ :white => SimpleColor(0xc0, 0xc0, 0xc0),
+ :bright_black => SimpleColor(0x80, 0x80, 0x80),
+ :grey => SimpleColor(0x80, 0x80, 0x80),
+ :gray => SimpleColor(0x80, 0x80, 0x80),
+ :bright_red => SimpleColor(0xff, 0x00, 0x00),
+ :bright_green => SimpleColor(0x00, 0xff, 0x00),
+ :bright_yellow => SimpleColor(0xff, 0xff, 0x00),
+ :bright_blue => SimpleColor(0x00, 0x00, 0xff),
+ :bright_magenta => SimpleColor(0xff, 0x00, 0xff),
+ :bright_cyan => SimpleColor(0x00, 0xff, 0xff),
+ :bright_white => SimpleColor(0xff, 0xff, 0xff))
+
+function htmlcolor(io::IO, color::SimpleColor)
+ if color.value isa Symbol
+ if color.value === :default
+ print(io, "initial")
+ elseif (fg = get(FACES, color.value, FACES[:default]).foreground) != SimpleColor(color.value)
+ htmlcolor(io, fg)
+ else
+ htmlcolor(io, get(HTML_BASIC_COLORS, color.value, SimpleColor(:default)))
+ end
+ else
+ (; r, g, b) = color.value
+ print(io, '#')
+ r < 0x10 && print(io, '0')
+ print(io, string(r, base=16))
+ g < 0x10 && print(io, '0')
+ print(io, string(g, base=16))
+ b < 0x10 && print(io, '0')
+ print(io, string(b, base=16))
+ end
+end
+
+const HTML_WEIGHT_MAP = Dict{Symbol, Int}(
+ :thin => 100,
+ :extralight => 200,
+ :light => 300,
+ :semilight => 300,
+ :normal => 400,
+ :medium => 500,
+ :semibold => 600,
+ :bold => 700,
+ :extrabold => 800,
+ :black => 900)
+
+function htmlstyle(io::IO, face::Face, lastface::Face=FACES[:default])
+ print(io, " """, ''' => "'"), '"')
+ face.weight == lastface.weight ||
+ print(io, "font-weight: ", get(HTML_WEIGHT_MAP, face.weight, 400), ';')
+ face.slant == lastface.slant ||
+ print(io, "font-style: ", String(face.slant), ';')
+ face.underline == lastface.underline ||
+ if face.underline isa Tuple # Color and style
+ color, style = face.underline
+ print(io, "text-decoration: ")
+ if !isnothing(color)
+ htmlcolor(io, color)
+ print(io, ' ')
+ end
+ print(io, if style == :straight "solid "
+ elseif style == :double "double "
+ elseif style == :curly "wavy "
+ elseif style == :dotted "dotted "
+ elseif style == :dashed "dashed "
+ else "" end)
+ print(io, "underline;")
+ elseif face.underline isa SimpleColor
+ print(io, "text-decoration: ")
+ htmlcolor(io, face.underline)
+ if lastface.underline isa Tuple && last(lastface.underline) != :straight
+ print(io, " solid")
+ end
+ print(io, " underline;")
+ else # must be a Bool
+ print(io, "text-decoration: ")
+ if lastface.underline isa SimpleColor
+ print(io, "currentcolor ")
+ elseif lastface.underline isa Tuple
+ first(lastface.underline) isa SimpleColor &&
+ print(io, "currentcolor ")
+ last(lastface.underline) != :straight &&
+ print(io, "straight ")
+ end
+ print(io, ifelse(face.underline, "underline;", "none;"))
+ end
+ face.strikethrough == lastface.strikethrough ||
+ print(io, ifelse(face.strikethrough,
+ "text-decoration: line-through",
+ ifelse(face.underline === false,
+ "text-decoration: none", "")))
+ print(io, "\">")
+end
+
+function show(io::IO, ::MIME"text/html", s::Union{<:StyledString, SubString{<:StyledString}}; wrap::Symbol=:pre)
+ htmlescape(str) = replace(str, '&' => "&", '<' => "<", '>' => ">")
+ buf = IOBuffer() # Avoid potential overhead in repeatadly printing a more complex IO
+ wrap == :none ||
+ print(buf, '<', String(wrap), '>')
+ lastface::Face = FACES[:default]
+ stylestackdepth = 0
+ for (str, styles) in eachstyle(s)
+ face = getface(styles)
+ link = let idx=findfirst(==(:link) ∘ first, styles)
+ if !isnothing(idx)
+ string(last(styles[idx]))::String
+ end end
+ !isnothing(link) && print(buf, "")
+ if face == FACES[:default]
+ print(buf, "" ^ stylestackdepth)
+ stylestackdepth = 0
+ elseif (lastface.inverse, lastface.foreground, lastface.background) !=
+ (face.inverse, face.foreground, face.background)
+ # We can't un-inherit colors well, so we just need to reset and apply
+ print(buf, "" ^ stylestackdepth)
+ htmlstyle(buf, face, FACES[:default])
+ stylestackdepth = 1
+ else
+ htmlstyle(buf, face, lastface)
+ stylestackdepth += 1
+ end
+ if wrap == :p
+ newpara = false
+ for para in eachsplit(str, "\n\n")
+ newpara && print(buf, "
\n")
+ print(buf, htmlescape(para))
+ newpara = true
+ end
+ else
+ print(buf, htmlescape(str))
+ end
+ !isnothing(link) && print(buf, "")
+ lastface = face
+ end
+ print(buf, "" ^ stylestackdepth)
+ wrap == :none ||
+ print(buf, "", String(wrap), '>')
+ write(io, take!(buf))
+ nothing
+end