Skip to content

Commit

Permalink
Implement Typst backend
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobjpeters committed Jun 8, 2024
1 parent 856a85b commit b6fc488
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
Poppler_jll = "9c32591e-4766-534b-9725-b71a8799265b"
Rsvg = "c4c386cf-5103-5370-be45-f3a111cca3b8"
Typst_jll = "eb4b1da6-20f6-5c66-9826-fdb8ad410d0e"
Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e"
tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5"

[weakdeps]
Expand All @@ -35,7 +35,7 @@ Makie = "0.21.2"
Poppler_jll = "21.9, 22, 23"
Rsvg = "1"
julia = "1.9"
Typst_jll = "0"
Typstry = "0.2"
tectonic_jll = "0"

[extras]
Expand Down
9 changes: 5 additions & 4 deletions ext/MakieTeXCairoMakieExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ CairoMakie.cairo_scatter_marker(v::NTuple{N, <: MakieTeX.AbstractDocument}) wher

# # Teximg

# Override `is_cairomakie_atomic_plot` to allow `TeXImg` to remain a unit,
# Override `is_cairomakie_atomic_plot` to allow `TeXImg` and `TypstImg` to remain a unit,
# instead of auto-decomposing into its component scatter plot.
CairoMakie.is_cairomakie_atomic_plot(plot::TeXImg) = true
CairoMakie.is_cairomakie_atomic_plot(plot::Union{TeXImg, TypstImg}) = true

# # Scatter markers

Expand Down Expand Up @@ -81,7 +81,8 @@ function CairoMakie.draw_marker(ctx, marker::MakieTeX.CachedSVG, pos, scale,
end


function CairoMakie.draw_marker(ctx, marker::Union{MakieTeX.CachedTEX, MakieTeX.CachedTeX, MakieTeX.CachedPDF}, pos, scale,
function CairoMakie.draw_marker(ctx, marker::Union{MakieTeX.CachedTEX, MakieTeX.CachedTeX, MakieTeX.CachedTypst, MakieTeX.CachedPDF},
pos, scale,
strokecolor #= unused =#, strokewidth #= unused =#,
marker_offset, rotation)
# get dimensions
Expand Down Expand Up @@ -259,4 +260,4 @@ function CairoMakie.draw_plot(scene::Makie.Scene, screen::CairoMakie.Screen, img
end
=#
=#
7 changes: 5 additions & 2 deletions src/MakieTeX.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module MakieTeX
using Makie
using Makie.MakieCore

using Colors, LaTeXStrings
using Colors, LaTeXStrings, Typstry
using Base64

# Patch for Makie.jl `@Block` macro error
Expand Down Expand Up @@ -36,6 +36,7 @@ include("layoutable.jl")

include("rendering/pdf_utils.jl")
include("rendering/tex.jl")
include("rendering/typst.jl")
include("rendering/pdf.jl")
include("rendering/svg.jl")

Expand All @@ -46,9 +47,11 @@ export PDFDocument, CachedPDF
export SVGDocument, CachedSVG
export dvi2svg, latex2dvi, rsvg2recordsurf, svg2rsvg
export teximg, teximg!, TeXImg
export LTeX
export typstimg, typstimg!, TypstImg
export LTeX, LTypst

export LaTeXStrings, LaTeXString, latexstring, @L_str
export Typstry, TypstString, @typst_str

"Try to write to `engine` and see what happens"
function try_tex_engine(engine::Cmd)
Expand Down
56 changes: 48 additions & 8 deletions src/layoutable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,63 @@ Makie.@Block LTeX begin
end
end

Makie.@Block LTypst begin
@attributes begin
"The Typst code to be compiled and drawn. Can be a String, a TypstDocument or a CachedTypst."
typst = "\\Typst"
"The density of pixels rendered (1 means 1 px == 1 pt)"
render_density::Int = 1
"Controls if the graphic is visible."
visible::Bool = true
"A scaling factor to resize the graphic."
scale::Float32 = 1.0
"The horizontal alignment of the graphic in its suggested boundingbox"
halign = :center
"The vertical alignment of the graphic in its suggested boundingbox"
valign = :center
"The counterclockwise rotation of the graphic in radians."
rotation::Float32 = 0f0
"The extra space added to the sides of the graphic boundingbox."
padding = (0f0, 0f0, 0f0, 0f0)
"The height setting of the graphic."
height = Auto()
"The width setting of the graphic."
width = Auto()
"Controls if the parent layout can adjust to this element's width"
tellwidth::Bool = true
"Controls if the parent layout can adjust to this element's height"
tellheight::Bool = true
"The align mode of the graphic in its parent GridLayout."
alignmode = Inside()
end
end

LTeX(x, tex; kwargs...) = LTeX(x; tex = tex, kwargs...)
LTypst(x, typst; kwargs...) = LTypst(x; typst = typst, kwargs...)

code(lt::LTeX) = lt.tex
code(lt::LTypst) = lt.typst

img!(::Type{LTeX}, args...; kwargs...) = teximg!(args...; kwargs...)
img!(::Type{LTypst}, args...; kwargs...) = typstimg!(args...; kwargs...)

_to_cachedtex(x) = CachedTEX(x)
_to_cachedtex(x::AbstractDocument) = Cached(x)
_to_cached(::Type{LTeX}, x) = CachedTEX(x)
_to_cached(::Type{LTypst}, x) = CachedTypst(x)
_to_cached(::Type, x::AbstractDocument) = Cached(x)

function Makie.initialize_block!(l::LTeX)
function Makie.initialize_block!(l::T) where T <: Union{LTeX, LTypst}

_code = code(l)
topscene = l.blockscene
layoutobservables = l.layoutobservables

textpos = Observable([Point3f(0, 0, 0)])
scale = Observable([Vec2f(1.0, 1.0)])

cached_tex = lift(collect tuple _to_cachedtex, l.tex)
cached = lift(collect tuple (x -> _to_cached(T, x)), _code)

t = teximg!(
topscene, cached_tex; position = textpos, visible = l.visible,
t = img!(T,
topscene, cached; position = textpos, visible = l.visible,
scale = scale, align = (:bottom, :left),
rotations = l.rotation,
markerspace = :pixel,
Expand All @@ -58,7 +98,7 @@ function Makie.initialize_block!(l::LTeX)

textbb = Ref(BBox(0, 1, 0, 1))

onany(l.tex, l.scale, l.rotation, l.padding) do tex, scale, rotation, padding
onany(_code, l.scale, l.rotation, l.padding) do code, scale, rotation, padding
textbb[] = rotatedrect(Makie.Rect2f(0,0,(t[1][][1].dims .* scale)...), rotation)
autowidth = Makie.width(textbb[]) + padding[1] + padding[2]
autoheight = Makie.height(textbb[]) + padding[3] + padding[4]
Expand All @@ -82,7 +122,7 @@ function Makie.initialize_block!(l::LTeX)


# trigger first update, otherwise bounds are wrong somehow
notify(l.tex)
notify(_code)
# trigger bbox
layoutobservables.suggestedbbox[] = layoutobservables.suggestedbbox[]

Expand Down
46 changes: 40 additions & 6 deletions src/recipe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,37 @@ $(Makie.ATTRIBUTES)
)
end

"""
typstimg(typst; position, ...)
typstimg!(ax_or_scene, tex; position, ...)
This recipe plots rendered `Typst` to your Figure or Scene.
There are three types of input you can provide:
- Any `String`, which is rendered to LaTeX cognizant of the figure's overall theme,
- A [`TypstDocument`](@ref) object, which is rendered to Typst directly, and can be customized by the user,
- A [`CachedTypst`](@ref) object, which is a pre-rendered Typst document.
`typst` may be a single one of these objects, or an array of them.
## Attributes
$(Makie.ATTRIBUTES)
"""
@recipe(TypstImg, typst) do scene
merge(
default_theme(scene),
Attributes(
render_density = 2,
align = (:center, :center),
scale = 1.0,
position = [Point2{Float32}(0)],
rotation = [0f0],
space = :data,
markerspace = :pixel
)
)
end

# First, handle the case of one or more abstract strings passed in!
# These are themable.

Expand Down Expand Up @@ -87,18 +118,21 @@ function offset_from_align(align::Tuple{Symbol, Symbol}, wh)::Vec2f
return Vec2f(x, y)
end

__bc_if_array(::Type{TeXImg}, x) = _bc_if_array(CachedTeX, x)
__bc_if_array(::Type{TypstImg}, x) = _bc_if_array(CachedTypst, x)

_bc_if_array(f, x) = f(x)
_bc_if_array(f, x::AbstractArray) = f.(x)

# scatter: marker size, rotations to determine everything
function Makie.plot!(plot::TeXImg)
function Makie.plot!(plot::T) where T <: Union{TeXImg, TypstImg}
# We always want to draw this at a 1:1 ratio, so increasing scale or
# changing dpi should rerender
plottable_images = lift(plot[1], plot.render_density, plot.scale) do cachedtex, render_density, scale
if cachedtex isa AbstractString || cachedtex isa AbstractArray{<: AbstractString}
to_array(_bc_if_array(CachedTEX, cachedtex))
plottable_images = lift(plot[1], plot.render_density, plot.scale) do ct, render_density, scale
if ct isa AbstractString || ct isa AbstractArray{<: AbstractString}
to_array(__bc_if_array(T, ct))
else
to_array(_bc_if_array(Cached, cachedtex))
to_array(_bc_if_array(Cached, ct))
end
end

Expand All @@ -113,7 +147,7 @@ function Makie.plot!(plot::TeXImg)
onany(plot, plottable_images, plot.position, plot.rotation, plot.align, plot.scale) do images, pos, rotations, align, scale
if length(images) != length(pos) && !(pos isa Makie.VecTypes)
# skip this update and let the next one propagate
@debug "TeXImg: Length of images ($(length(images))) != length of positions ($(length(pos))). Skipping this update."
@debug "$T: Length of images ($(length(images))) != length of positions ($(length(pos))). Skipping this update."
return
end

Expand Down
22 changes: 11 additions & 11 deletions src/rendering/pdf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ end
# Rendering functions for the resulting Cairo surfaces and images

"""
page2img(tex::CachedTeX, page::Int; scale = 1, render_density = 1)
page2img(ct::Union{CachedTeX, CachedTypst}, page::Int; scale = 1, render_density = 1)
Renders the `page` of the given `CachedTeX` object to an image, with the given `scale` and `render_density`.
Renders the `page` of the given `CachedTeX` or `CachedTypst` object to an image, with the given `scale` and `render_density`.
This function reads the PDF using Poppler and renders it to a Cairo surface, which is then read as an image.
"""
function page2img(tex::Union{CachedTeX, CachedPDF}, page::Int; scale = 1, render_density = 1)
document = update_handle!(tex)
page2img(document, page, size(tex); scale, render_density)
function page2img(ct::Union{CachedTeX, CachedTypst, CachedPDF}, page::Int; scale = 1, render_density = 1)
document = update_handle!(ct)
page2img(document, page, size(ct); scale, render_density)
end

function page2img(document::Ptr{Cvoid}, page::Int, tex_dims::Tuple; scale = 1, render_density = 1)
Expand Down Expand Up @@ -136,7 +136,7 @@ function page2img(document::Ptr{Cvoid}, page::Int, tex_dims::Tuple; scale = 1, r

end

firstpage2img(tex; kwargs...) = page2img(tex, 0; kwargs...)
firstpage2img(ct; kwargs...) = page2img(ct, 0; kwargs...)

function page2recordsurf(document::Ptr{Cvoid}, page::Int; scale = 1, render_density = 1)
w, h = pdf_get_page_size(document, page)
Expand Down Expand Up @@ -167,16 +167,16 @@ function page2recordsurf(document::Ptr{Cvoid}, page::Int; scale = 1, render_dens

end

firstpage2recordsurf(tex; kwargs...) = page2recordsurf(tex, 0; kwargs...)
firstpage2recordsurf(ct; kwargs...) = page2recordsurf(ct, 0; kwargs...)

function recordsurf2img(tex::CachedTeX, render_density = 1)
function recordsurf2img(ct::Union{CachedTeX, CachedTypst}, render_density = 1)

# We can find the final dimensions (in pixel units) of the Rsvg image.
# Then, it's possible to store the image in a native Julia array,
# which simplifies the process of rendering.
# Cairo does not draw "empty" pixels, so we need to fill here
w = ceil(Int, tex.dims[1] * render_density)
h = ceil(Int, tex.dims[2] * render_density)
w = ceil(Int, ct.dims[1] * render_density)
h = ceil(Int, ct.dims[2] * render_density)

img = fill(Colors.ARGB32(0,0,0,0), w, h)

Expand All @@ -187,7 +187,7 @@ function recordsurf2img(tex::CachedTeX, render_density = 1)
c = Cairo.CairoContext(cs)

# Render the parsed SVG to a Cairo context
render_surface(c, tex.surf)
render_surface(c, ct.surf)

# The image is rendered transposed, so we need to flip it.
return rotr90(permutedims(img))
Expand Down
Loading

0 comments on commit b6fc488

Please sign in to comment.