Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outputting to LaTeX coloring #108

Open
MilesCranmer opened this issue Jan 29, 2025 · 3 comments
Open

Outputting to LaTeX coloring #108

MilesCranmer opened this issue Jan 29, 2025 · 3 comments

Comments

@MilesCranmer
Copy link
Member

x-ref MilesCranmer/AirspeedVelocity.jl#74

This is probably out-of-scope but I was wondering if there was any way to get a LaTeX-style coloring of text, for use in markdown-like contexts such as on GitHub? For example, $\color{red}\text{this text is shown in red.}$

This is mostly useful for GitHub actions that output GitHub comments – I'd like to control coloring of those via StyledStrings.jl.

@LilithHafner
Copy link
Member

IDK if this feature belongs in a stdlib but I definitely want it to exist somewhere.

@LilithHafner
Copy link
Member

Loosely related: JuliaLang/julia#35936

@tecosaur
Copy link
Collaborator

This reminds me, I have a stashed commit that implements most of show(::IO, ::MIME"text/latex", ::AnnotatedString).

diff
@@ -212,7 +212,7 @@ function termstyle(io::IO, face::Face, lastface::Face=getface())
                              ANSI_STYLE_CODES.start_underline,
                              ANSI_STYLE_CODES.end_underline))
         end
-    face.strikethrough == lastface.strikethrough || !haskey(Base.current_terminfo, :smxx) ||
+    face.strikethrough == lastface.strikethrough || !haskey(Base.current_terminfo, :enter_strikeout_mode) ||
         print(io, ifelse(face.strikethrough === true,
                          ANSI_STYLE_CODES.start_strikethrough,
                          ANSI_STYLE_CODES.end_strikethrough))
@@ -295,6 +295,8 @@ function write(io::IO, aio::Base.AnnotatedIOBuffer)
     end
 end
 
+
+
 """
 A mapping between ANSI named colors and 8-bit colors for use in HTML
 representations.
@@ -319,18 +321,16 @@ const HTML_BASIC_COLORS = Dict{Symbol, SimpleColor}(
     :bright_cyan => SimpleColor(0x26, 0xc6, 0xda),
     :bright_white => SimpleColor(0xf6, 0xf5, 0xf4))
 
-function htmlcolor(io::IO, color::SimpleColor)
+function hexcolor(io::IO, color::SimpleColor)
     if color.value isa Symbol
         if color.value === :default
-            print(io, "initial")
         elseif (fg = get(FACES.current[], color.value, getface()).foreground) != SimpleColor(color.value)
-            htmlcolor(io, fg)
+            hexcolor(io, fg)
         else
-            htmlcolor(io, get(HTML_BASIC_COLORS, color.value, SimpleColor(:default)))
+            hexcolor(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')
@@ -340,6 +340,15 @@ function htmlcolor(io::IO, color::SimpleColor)
     end
 end
 
+function htmlcolor(io::IO, color::SimpleColor)
+    if color.value isa Symbol && color.value === :default
+        print(io, "initial")
+    else
+        print(io, '#')
+        hexcolor(io, color)
+    end
+end
+
 const HTML_WEIGHT_MAP = Dict{Symbol, Int}(
     :thin => 100,
     :extralight => 200,
@@ -469,3 +478,113 @@ function show(io::IO, ::MIME"text/html", s::Union{<:AnnotatedString, SubString{<
     write(io, take!(buf))
     nothing
 end
+
+
+
+const TEX_WEIGHT_CODES = Dict{Symbol, String}(
+    :thin => "ul",
+    :extralight => "el",
+    :light => "l",
+    :semilight => "sl",
+    :normal => "m",
+    :medium => "m",
+    :semibold => "sb",
+    :bold => "b",
+    :extrabold => "eb",
+    :black => "ub")
+
+const TEX_SLANT_CODES = Dict{Symbol, String}(
+    :italic => "it",
+    :slanted => "sl",
+    :oblique => "sl"
+)
+
+function texstyle(io::IO, face::Face, lastface::Face=getface())
+    created_group_count = 0
+    if face.font != lastface.font ||
+        face.height != lastface.height ||
+        face.weight != lastface.weight ||
+        face.slant != lastface.slant
+        print(io, '{')
+        created_group_count += 1
+        if face.font != lastface.font
+            # TODO
+        end
+        if face.height != lastface.height
+            height = string(face.height ÷ 10)
+            print(io, "\\fontsize{", height, "pt}{", height, "pt}")
+        end
+        if face.weight != lastface.weight
+            print(io, "\\fontseries{", get(TEX_WEIGHT_CODES, face.weight, "m"), "}")
+        end
+        if face.slant != lastface.slant
+            print(io, "\\fontshape{", get(TEX_SLANT_CODES, face.slant, "n"), "}")
+        end
+        print(io, "\\selectfont ")
+    end
+    foreground, background =
+        ifelse(face.inverse === true,
+               (face.background, face.foreground),
+               (face.foreground, face.background))
+    lastforeground, lastbackground =
+        ifelse(lastface.inverse === true,
+               (lastface.background, lastface.foreground),
+               (lastface.foreground, lastface.background))
+    if background != lastbackground
+        print(io, "\\colorbox[HTML]{")
+        hexcolor(io, background)
+        print(io, "}{")
+        created_group_count += 1
+    end
+    if face.underline != lastface.underline && face.underline !== false
+        print(io, "\\underline{")
+        created_group_count += 1
+    end
+    if face.strikethrough != lastface.strikethrough && face.strikethrough !== false
+        print(io, "\\sout{")
+        created_group_count += 1
+    end
+    if foreground != lastforeground
+        if created_group_count == 0
+            print(io, '{')
+            created_group_count += 1
+        end
+        print(io, "\\color[HTML]{")
+        hexcolor(io, foreground)
+        print(io, "}")
+    end
+    created_group_count
+end
+
+function show(io::IO, ::MIME"text/latex", s::Union{<:AnnotatedString, SubString{<:AnnotatedString}})
+    texescape(str) = replace(str, '\\' => "\\char92{}", '^' => "\\char94{}", "~" => "\\char126{}",
+                             r"[\\{}$%&_#]" => s"\\\0")
+    buf = IOBuffer() # Avoid potential overhead in repeatadly printing a more complex IO
+    lastface::Face = getface()
+    groupdepth = 0
+    for (str, styles) in eachregion(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, "\\href{", texescape(link), "}{")
+        if face == getface()
+            print(buf, '}' ^ groupdepth)
+            groupdepth = 0
+        elseif (lastface.background, lastface.underline, lastface.strikethrough) !=
+            (face.background, face.underline, face.strikethrough)
+            # We can't un-inherit backgrounds, underlines, or strikethroughts
+            print(buf, '}' ^ groupdepth)
+            groupdepth = texstyle(buf, face, getface())
+        else
+            groupdepth += texstyle(buf, face, lastface)
+        end
+        print(buf, texescape(str))
+        !isnothing(link) && print(buf, '}')
+        lastface = face
+    end
+    print(buf, '}' ^ groupdepth)
+    write(io, take!(buf))
+    nothing
+end

IIRC it's a little bit of elbow grease off actually working well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants