Skip to content

Commit 98e9af4

Browse files
committed
Add text/html show method for styled strings
1 parent ea24b53 commit 98e9af4

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

base/strings/io.jl

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,3 +1059,178 @@ function show(io::IO, c::StyledChar)
10591059
show(io, c.char)
10601060
end
10611061
end
1062+
1063+
"""
1064+
A mapping between ANSI named colors and 8-bit colors for use in HTML
1065+
representations.
1066+
"""
1067+
const HTML_BASIC_COLORS = Dict{Symbol, SimpleColor}(
1068+
:black => SimpleColor(0x00, 0x00, 0x00),
1069+
:red => SimpleColor(0x80, 0x00, 0x00),
1070+
:green => SimpleColor(0x00, 0x80, 0x00),
1071+
:yellow => SimpleColor(0x80, 0x80, 0x00),
1072+
:blue => SimpleColor(0x00, 0x00, 0x80),
1073+
:magenta => SimpleColor(0x80, 0x00, 0x80),
1074+
:cyan => SimpleColor(0x00, 0x80, 0x80),
1075+
:white => SimpleColor(0xc0, 0xc0, 0xc0),
1076+
:bright_black => SimpleColor(0x80, 0x80, 0x80),
1077+
:grey => SimpleColor(0x80, 0x80, 0x80),
1078+
:gray => SimpleColor(0x80, 0x80, 0x80),
1079+
:bright_red => SimpleColor(0xff, 0x00, 0x00),
1080+
:bright_green => SimpleColor(0x00, 0xff, 0x00),
1081+
:bright_yellow => SimpleColor(0xff, 0xff, 0x00),
1082+
:bright_blue => SimpleColor(0x00, 0x00, 0xff),
1083+
:bright_magenta => SimpleColor(0xff, 0x00, 0xff),
1084+
:bright_cyan => SimpleColor(0x00, 0xff, 0xff),
1085+
:bright_white => SimpleColor(0xff, 0xff, 0xff))
1086+
1087+
function htmlcolor(io::IO, color::SimpleColor)
1088+
if color.value isa Symbol
1089+
if color.value === :default
1090+
print(io, "initial")
1091+
elseif (fg = get(FACES.current[], color.value, getface()).foreground) != SimpleColor(color.value)
1092+
htmlcolor(io, fg)
1093+
else
1094+
htmlcolor(io, get(HTML_BASIC_COLORS, color.value, SimpleColor(:default)))
1095+
end
1096+
else
1097+
(; r, g, b) = color.value
1098+
print(io, '#')
1099+
r < 0x10 && print(io, '0')
1100+
print(io, string(r, base=16))
1101+
g < 0x10 && print(io, '0')
1102+
print(io, string(g, base=16))
1103+
b < 0x10 && print(io, '0')
1104+
print(io, string(b, base=16))
1105+
end
1106+
end
1107+
1108+
const HTML_WEIGHT_MAP = Dict{Symbol, Int}(
1109+
:thin => 100,
1110+
:extralight => 200,
1111+
:light => 300,
1112+
:semilight => 300,
1113+
:normal => 400,
1114+
:medium => 500,
1115+
:semibold => 600,
1116+
:bold => 700,
1117+
:extrabold => 800,
1118+
:black => 900)
1119+
1120+
function htmlstyle(io::IO, face::Face, lastface::Face=getface())
1121+
print(io, "<span style=\"")
1122+
face.font == lastface.font ||
1123+
print(io, "font-family: \"",
1124+
replace(face.font, '"' => "&quot;", ''' => "&#39;"), '"')
1125+
face.height == lastface.height ||
1126+
print(io, "font-size: ", string(face.height ÷ 10), "pt;")
1127+
face.weight == lastface.weight ||
1128+
print(io, "font-weight: ", get(HTML_WEIGHT_MAP, face.weight, 400), ';')
1129+
face.slant == lastface.slant ||
1130+
print(io, "font-style: ", String(face.slant), ';')
1131+
foreground, background =
1132+
ifelse(face.inverse === true,
1133+
(face.background, face.foreground),
1134+
(face.foreground, face.background))
1135+
lastforeground, lastbackground =
1136+
ifelse(lastface.inverse === true,
1137+
(lastface.background, lastface.foreground),
1138+
(lastface.foreground, lastface.background))
1139+
if foreground != lastforeground
1140+
print(io, "color: ")
1141+
htmlcolor(io, foreground)
1142+
print(io, ';')
1143+
end
1144+
if background != lastbackground
1145+
print(io, "background-color: ")
1146+
htmlcolor(io, background)
1147+
print(io, ';')
1148+
end
1149+
face.underline == lastface.underline ||
1150+
if face.underline isa Tuple # Color and style
1151+
color, style = face.underline
1152+
print(io, "text-decoration: ")
1153+
if !isnothing(color)
1154+
htmlcolor(io, color)
1155+
print(io, ' ')
1156+
end
1157+
print(io, if style == :straight "solid "
1158+
elseif style == :double "double "
1159+
elseif style == :curly "wavy "
1160+
elseif style == :dotted "dotted "
1161+
elseif style == :dashed "dashed "
1162+
else "" end)
1163+
print(io, "underline;")
1164+
elseif face.underline isa SimpleColor
1165+
print(io, "text-decoration: ")
1166+
htmlcolor(io, face.underline)
1167+
if lastface.underline isa Tuple && last(lastface.underline) != :straight
1168+
print(io, " solid")
1169+
end
1170+
print(io, " underline;")
1171+
else # must be a Bool
1172+
print(io, "text-decoration: ")
1173+
if lastface.underline isa SimpleColor
1174+
print(io, "currentcolor ")
1175+
elseif lastface.underline isa Tuple
1176+
first(lastface.underline) isa SimpleColor &&
1177+
print(io, "currentcolor ")
1178+
last(lastface.underline) != :straight &&
1179+
print(io, "straight ")
1180+
end
1181+
print(io, ifelse(face.underline, "underline;", "none;"))
1182+
end
1183+
face.strikethrough == lastface.strikethrough ||
1184+
print(io, ifelse(face.strikethrough,
1185+
"text-decoration: line-through",
1186+
ifelse(face.underline === false,
1187+
"text-decoration: none", "")))
1188+
print(io, "\">")
1189+
end
1190+
1191+
function show(io::IO, ::MIME"text/html", s::Union{<:StyledString, SubString{<:StyledString}}; wrap::Symbol=:pre)
1192+
htmlescape(str) = replace(str, '&' => "&amp;", '<' => "&lt;", '>' => "&gt;")
1193+
buf = IOBuffer() # Avoid potential overhead in repeatadly printing a more complex IO
1194+
wrap == :none ||
1195+
print(buf, '<', String(wrap), '>')
1196+
lastface::Face = getface()
1197+
stylestackdepth = 0
1198+
for (str, styles) in eachstyle(s)
1199+
face = getface(styles)
1200+
link = let idx=findfirst(==(:link) first, styles)
1201+
if !isnothing(idx)
1202+
string(last(styles[idx]))::String
1203+
end end
1204+
!isnothing(link) && print(buf, "<a href=\"", link, "\">")
1205+
if face == getface()
1206+
print(buf, "</span>" ^ stylestackdepth)
1207+
stylestackdepth = 0
1208+
elseif (lastface.inverse, lastface.foreground, lastface.background) !=
1209+
(face.inverse, face.foreground, face.background)
1210+
# We can't un-inherit colors well, so we just need to reset and apply
1211+
print(buf, "</span>" ^ stylestackdepth)
1212+
htmlstyle(buf, face, getface())
1213+
stylestackdepth = 1
1214+
else
1215+
htmlstyle(buf, face, lastface)
1216+
stylestackdepth += 1
1217+
end
1218+
if wrap == :p
1219+
newpara = false
1220+
for para in eachsplit(str, "\n\n")
1221+
newpara && print(buf, "</p>\n<p>")
1222+
print(buf, htmlescape(para))
1223+
newpara = true
1224+
end
1225+
else
1226+
print(buf, htmlescape(str))
1227+
end
1228+
!isnothing(link) && print(buf, "</a>")
1229+
lastface = face
1230+
end
1231+
print(buf, "</span>" ^ stylestackdepth)
1232+
wrap == :none ||
1233+
print(buf, "</", String(wrap), '>')
1234+
write(io, take!(buf))
1235+
nothing
1236+
end

0 commit comments

Comments
 (0)