diff --git a/Project.toml b/Project.toml index a55ba3a..4bfc1aa 100644 --- a/Project.toml +++ b/Project.toml @@ -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] @@ -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] diff --git a/docs/src/api.md b/docs/src/api.md index 5cf90aa..182b8e5 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -37,10 +37,12 @@ SVGDocument PDFDocument EPSDocument TEXDocument +TypstDocument ``` ### Cached document types ```@docs CachedTEX +CachedTypst CachedPDF CachedSVG CachedEPS diff --git a/docs/src/formats.md b/docs/src/formats.md index 837d1da..e734989 100644 --- a/docs/src/formats.md +++ b/docs/src/formats.md @@ -58,6 +58,21 @@ scatter(fig[2, 1], rand(10), rand(10), marker=tex_document, markersize = 50) fig ``` +## Typst + +```@example main +using MakieTeX, CairoMakie + +typst_string = typst"$ integral_0^pi sin(x)^2 diff x $"; +typst_document = TypstDocument(typst_string); +cached_typst = CachedTypst(typst_document); +cached_pdf = convert(CachedPDF, cached_typst); + +fig = Figure(size=(100, 100)); +LTeX(fig[1, 1], cached_pdf); +fig +``` + ## PDF ```@example main diff --git a/docs/src/index.md b/docs/src/index.md index ba53963..53586af 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -55,9 +55,7 @@ teximg(raw""" Rendering can occur either to a bitmap (for GL backends) or to a Cairo surface (for CairoMakie). Both of these have APIs ([`rasterize`](@ref) and [`draw_to_cairo_surface`](@ref)). -Each rendering format has its own complexities, so the rendering pipelines are usually separate. SVG uses librsvg, PDF and EPS use Poppler directly, and TeX uses the available local TeX render (if not, `tectonic` is bundled with MakieTeX) to render to a PDF, which then follows the Poppler pipeline. - -A hypothetical future Typst backend would likely also be a Typst -> PDF -> Poppler pipeline. [Typst_jll](https://github.com/JuliaBinaryWrappers/Typst_jll.jl) already exists, so it would be fairly easy to bundle. +Each rendering format has its own complexities, so the rendering pipelines are usually separate. SVG uses librsvg while PDF and EPS use Poppler directly. TeX uses the available local TeX renderer (if not, `tectonic` is bundled with MakieTeX) and Typst uses Typst_jll.jl to render to a PDF, which then each follow the Poppler pipeline. ### Makie @@ -67,4 +65,4 @@ When rendering to Makie, MakieTeX rasterizes the document to a bitmap by default ```@raw html -``` \ No newline at end of file +``` diff --git a/ext/MakieTeXCairoMakieExt.jl b/ext/MakieTeXCairoMakieExt.jl index bb0ddcd..f64405b 100644 --- a/ext/MakieTeXCairoMakieExt.jl +++ b/ext/MakieTeXCairoMakieExt.jl @@ -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 @@ -259,4 +260,4 @@ function CairoMakie.draw_plot(scene::Makie.Scene, screen::CairoMakie.Screen, img end -=# \ No newline at end of file +=# diff --git a/src/MakieTeX.jl b/src/MakieTeX.jl index 65b7646..a738443 100644 --- a/src/MakieTeX.jl +++ b/src/MakieTeX.jl @@ -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 @@ -36,12 +36,14 @@ include("layoutable.jl") include("rendering/pdf_utils.jl") include("rendering/tex.jl") +include("rendering/typst.jl") include("rendering/pdf.jl") include("rendering/svg.jl") export Cached export TeXDocument, CachedTeX export TEXDocument, CachedTEX +export TypstDocument, CachedTypst export PDFDocument, CachedPDF export SVGDocument, CachedSVG export dvi2svg, latex2dvi, rsvg2recordsurf, svg2rsvg @@ -49,6 +51,7 @@ export teximg, teximg!, TeXImg export LTeX 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) diff --git a/src/recipe.jl b/src/recipe.jl index 8e70cc1..7752a1d 100644 --- a/src/recipe.jl +++ b/src/recipe.jl @@ -143,4 +143,3 @@ function Makie.plot!(plot::TeXImg) markerspace = plot.markerspace, ) end - diff --git a/src/rendering/pdf.jl b/src/rendering/pdf.jl index 1b91554..032dbd1 100644 --- a/src/rendering/pdf.jl +++ b/src/rendering/pdf.jl @@ -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) @@ -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) @@ -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) @@ -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)) diff --git a/src/rendering/pdf_utils.jl b/src/rendering/pdf_utils.jl index 5e88280..93737e1 100644 --- a/src/rendering/pdf_utils.jl +++ b/src/rendering/pdf_utils.jl @@ -144,4 +144,45 @@ Crop a PDF file using Ghostscript. This alters the crop box but does not actually remove elements. """ function crop_pdf(path::String; margin = _PDFCROP_DEFAULT_MARGINS[]) + # if pdf_num_pages("temp.pdf") > 1 + # @warn("The PDF has more than 1 page! Choosing the first page.") + # end + + # Generate the cropping margins + bbox = get_pdf_bbox(path) + crop_box = ( + bbox[1] - margin[1], + bbox[2] - margin[2], + bbox[3] + margin[3], + bbox[4] + margin[4] + ) + crop_cmd = join(crop_box, " ") + + + out = Pipe() + err = Pipe() + try + redirect_stderr(err) do + redirect_stdout(out) do + Ghostscript_jll.gs() do gs_exe + run(`$gs_exe -o temp_cropped.pdf -sDEVICE=pdfwrite -c "[/CropBox [$crop_cmd]" -c "/PAGES pdfmark" -f $path`) + end + end + end + catch e + finally + close(out.in) + close(err.in) + if !isfile("temp_cropped.pdf") + println("`gs` failed to crop the PDF!") + println("Files in temp directory are:\n" * join(readdir(), ',')) + printstyled("Stdout\n", bold=true, color = :blue) + println(read(out, String)) + printstyled("Stderr\n", bold=true, color = :red) + println(read(err, String)) + error() + end + end + + return isfile("temp_cropped.pdf") ? read("temp_cropped.pdf", String) : read(path, String) end diff --git a/src/rendering/tex.jl b/src/rendering/tex.jl index 8ad3aac..580800b 100644 --- a/src/rendering/tex.jl +++ b/src/rendering/tex.jl @@ -29,6 +29,7 @@ function compile_latex( # First, create the tex file and write the document to it. touch("temp.tex") + path = "temp.pdf" file = open("temp.tex", "w") print(file, document) close(file) @@ -48,8 +49,8 @@ function compile_latex( suc = success(latex) close(out.in) close(err.in) - if !isfile("temp.pdf") - println("Latex did not write temp.pdf! Using the $(tex_engine) engine.") + if !isfile(path) + println("Latex did not write $(path)! Using the $(tex_engine) engine.") println("Files in temp directory are:\n" * join(readdir(), ',')) printstyled("Stdout\n", bold=true, color = :blue) println(read(out, String)) @@ -58,47 +59,7 @@ function compile_latex( error() end finally - - # if pdf_num_pages("temp.pdf") > 1 - # @warn("The PDF has more than 1 page! Choosing the first page.") - # end - - # Generate the cropping margins - bbox = get_pdf_bbox("temp.pdf") - crop_box = ( - bbox[1] - _PDFCROP_DEFAULT_MARGINS[][1], - bbox[2] - _PDFCROP_DEFAULT_MARGINS[][2], - bbox[3] + _PDFCROP_DEFAULT_MARGINS[][3], - bbox[4] + _PDFCROP_DEFAULT_MARGINS[][4], - ) - crop_cmd = join(crop_box, " ") - - - out = Pipe() - err = Pipe() - try - redirect_stderr(err) do - redirect_stdout(out) do - Ghostscript_jll.gs() do gs_exe - run(`$gs_exe -o temp_cropped.pdf -sDEVICE=pdfwrite -c "[/CropBox [$crop_cmd]" -c "/PAGES pdfmark" -f temp.pdf`) - end - end - end - catch e - finally - close(out.in) - close(err.in) - if !isfile("temp_cropped.pdf") - println("`gs` failed to crop the PDF!") - println("Files in temp directory are:\n" * join(readdir(), ',')) - printstyled("Stdout\n", bold=true, color = :blue) - println(read(out, String)) - printstyled("Stderr\n", bold=true, color = :red) - println(read(err, String)) - error() - end - end - return isfile("temp_cropped.pdf") ? read("temp_cropped.pdf", String) : read("temp.pdf", String) + return crop_pdf(path) end end end diff --git a/src/rendering/typst.jl b/src/rendering/typst.jl new file mode 100644 index 0000000..2df9acf --- /dev/null +++ b/src/rendering/typst.jl @@ -0,0 +1,66 @@ +#= +# Typst rendering +=# + +function rasterize(ct::CachedTypst, scale::Int64 = 1) + return page2img(ct, ct.doc.page; scale) +end + +# The main compilation method - compiles arbitrary Typst documents +""" + compile_typst(document::AbstractString) + +Compile the given document as a String and return the resulting PDF (also as a String). +""" +function compile_typst(document::AbstractString) + #= + Typst_jll v0.11+ supports compiling from `stdin`. + It does not yet support compiling to `stdout`. + + See also: + https://github.com/typst/typst/issues/410 + https://github.com/typst/typst/pull/3339 + =# + return mktempdir() do dir + cd(dir) do + + # First, create the typst file and write the document to it. + touch("temp.typ") + file = open("temp.typ", "w") + path = "temp.pdf" + print(file, document) + close(file) + + # Now, we run the latexmk command in a pipeline so that we can redirect stdout and stderr to internal containers. + # First we establish these pipelines: + out = Pipe() + err = Pipe() + + try + # `pipeline` is not yet supported for `TypstCommand` + redirect_stdio(stdout=out, stderr=err) do + run(ignorestatus(typst`compile temp.typ`)) + end + + close(out.in) + close(err.in) + if !isfile(path) + println("Typst did not write $(path)! Using Typst_jll.jl.") + println("Files in temp directory are:\n" * join(readdir(), ',')) + printstyled("Stdout\n", bold=true, color = :blue) + println(read(out, String)) + printstyled("Stderr\n", bold=true, color = :red) + println(read(err, String)) + error() + end + finally + return crop_pdf(path) + end + end + end +end + + +compile_typst(doc::TypstDocument) = compile_typst(String(doc.doc)) + +typst2pdf(args...) = compile_typst(args...) diff --git a/src/types.jl b/src/types.jl index d586fa4..59060c5 100644 --- a/src/types.jl +++ b/src/types.jl @@ -270,6 +270,62 @@ Available keyword arguments are: """ texdoc(contents; kwargs...) = TEXDocument(contents, true; kwargs...) +struct TypstDocument <: AbstractDocument + contents::String + page::Int +end +TypstDocument(contents) = TypstDocument(contents, 0) +Cached(x::TypstDocument) = CachedPDF(x) +getdoc(doc::TypstDocument) = doc.contents +mimetype(::TypstDocument) = MIME"text/typst"() + +""" + TypstDocument(contents::AbstractString, add_defaults::Bool; preamble) + +This constructor function creates a `struct` of type `TypstDocument`. +All arguments are to be passed as strings. + +If `add_defaults` is `false`, then we will *not* automatically add document structure. +Note that in this case, keyword arguments will be disregarded and `contents` must be +a complete Typst document. + +Available keyword arguments are: +- `preamble`: arbitrary code inserted prior to the `contents`. Default: `""`. + +See also [`CachedTypst`](@ref), [`compile_typst`](@ref), etc. +""" +function TypstDocument( + contents::AbstractString, + add_defaults::Bool; + preamble::AbstractString = "", + ) + if add_defaults + return TypstDocument( + """ + $(preamble) + + $(contents) + """ + ) + else + return TypstDocument(contents) + end +end +TypstDocument(ts::TypstString) = TypstDocument(ts, true) + +""" + typstdoc(contents::AbstractString; kwargs...) + +A shorthand for `TypstDocument(contents, add_defaults=true; kwargs...)`. + +Available keyword arguments are: + +- `preamble`: arbitrary code inserted prior to the `contents`. Default: `""`. + +""" +typst_doc(contents; kwargs...) = TypstDocument(contents, true; kwargs...) + + #= # Cached documents @@ -394,14 +450,78 @@ $(FIELDS) It is also possible to manually construct a `CachedTEX` with `nothing` in the `doc` field, if you just want to insert a pre-rendered PDF into your figure. """ -function CachedTEX(doc::TEXDocument; kwargs...) +CachedTEX(doc::TEXDocument; kwargs...) = cached_doc(CachedTEX, latex2pdf, doc; kwargs...) + +function CachedTEX(str::String; kwargs...) + return CachedTEX(implant_text(str); kwargs...) +end + +function CachedTEX(x::LaTeXString; kwargs...) + x = convert(String, x) + return if first(x) == "\$" && last(x) == "\$" + CachedTEX(implant_math(x[2:end-1]); kwargs...) + else + CachedTEX(implant_text(x); kwargs...) + end +end + +CachedTEX(pdf::Vector{UInt8}; kwargs...) = cached_pdf(CachedTEX, pdf; kwargs...) + +# do not rerun the pipeline on CachedTEX +CachedTEX(ct::CachedTEX) = ct - pdf = Vector{UInt8}(latex2pdf(convert(String, doc); kwargs...)) +struct CachedTypst <: AbstractCachedDocument + "The original `TypstDocument` which is compiled." + doc::TypstDocument + "The resulting compiled PDF" + pdf::Vector{UInt8} + "A pointer to the Poppler handle of the PDF. May be randomly GC'ed by Poppler." + ptr::Ref{Ptr{Cvoid}} # Poppler handle + "A surface to which Poppler has drawn the PDF. Permanent and cached." + surf::CairoSurface + "The dimensions of the PDF page, for ease of access." + dims::Tuple{Float64, Float64} +end +getdoc(doc::CachedTypst) = getdoc(doc.doc) +mimetype(::CachedTypst) = MIME"text/typst"() + +""" + CachedTypst(doc::TypstDocument) + +Compile a `TypstDocument`, compile it and return the cached Typst object. + +A `CachedTypst` struct stores the document and its compiled form, as well as some +pointers to in-program versions of it. It also stores the page dimensions. + +The constructor stores the following fields: +$(FIELDS) + +!!! note + This is a `mutable struct` because the pointer to the Poppler handle can change. + TODO: make this an immutable struct with a Ref to the handle?? OR maybe even the surface itself... + +!!! note + It is also possible to manually construct a `CachedTypst` with `nothing` in the `doc` field, + if you just want to insert a pre-rendered PDF into your figure. +""" +CachedTypst(doc::TypstDocument) = cached_doc(CachedTypst, typst2pdf, doc) + +function CachedTypst(str::Union{String, TypstString}; kwargs...) + CachedTypst(TypstDocument(str); kwargs...) +end + +CachedTypst(pdf::Vector{UInt8}; kwargs...) = cached_pdf(CachedTypst, pdf; kwargs...) + +# do not rerun the pipeline on CachedTypst +CachedTypst(ct::CachedTypst) = ct + +function cached_doc(T, f, doc; kwargs...) + pdf = Vector{UInt8}(f(convert(String, doc); kwargs...)) ptr = load_pdf(pdf) surf = page2recordsurf(ptr, doc.page) dims = (pdf_get_page_size(ptr, doc.page)) - ct = CachedTEX( + ct = T( doc, pdf, Ref(ptr), @@ -412,25 +532,12 @@ function CachedTEX(doc::TEXDocument; kwargs...) return ct end -function CachedTEX(str::String; kwargs...) - return CachedTEX(implant_text(str); kwargs...) -end - -function CachedTEX(x::LaTeXString; kwargs...) - x = convert(String, x) - return if first(x) == "\$" && last(x) == "\$" - CachedTEX(implant_math(x[2:end-1]); kwargs...) - else - CachedTEX(implant_text(x); kwargs...) - end -end - -function CachedTEX(pdf::Vector{UInt8}; kwargs...) +function cached_pdf(T, pdf; kwargs...) ptr = load_pdf(pdf) surf = firstpage2recordsurf(ptr) dims = pdf_get_page_size(ptr, 0) - ct = CachedTEX( + ct = T( nothing, pdf, Ref(ptr), @@ -440,27 +547,27 @@ function CachedTEX(pdf::Vector{UInt8}; kwargs...) return ct end -# do not rerun the pipeline on CachedTEX -CachedTEX(ct::CachedTEX) = ct - -function update_handle!(ct::CachedTEX) +function update_handle!(ct::Union{CachedTEX, CachedTypst}) ct.ptr[] = load_pdf(ct.pdf) return ct.ptr[] end -Base.convert(::Type{CachedPDF}, ct::CachedTEX) = CachedPDF(PDFDocument(String(deepcopy(ct.pdf)), ct.doc.page), ct.ptr, ct.dims, ct.surf, Ref{Tuple{Matrix{ARGB32}, Float64}}((Matrix{ARGB32}(undef, 0, 0), 0))) -Base.convert(::Type{PDFDocument}, ct::CachedTEX) = PDFDocument(String(deepcopy(ct.pdf)), ct.doc.page) +Base.convert(::Type{CachedPDF}, ct::Union{CachedTEX, CachedTypst}) = CachedPDF(PDFDocument(String(deepcopy(ct.pdf)), ct.doc.page), ct.ptr, ct.dims, ct.surf, Ref{Tuple{Matrix{ARGB32}, Float64}}((Matrix{ARGB32}(undef, 0, 0), 0))) +Base.convert(::Type{PDFDocument}, ct::Union{CachedTEX, CachedTypst}) = PDFDocument(String(deepcopy(ct.pdf)), ct.doc.page) -function Base.show(io::IO, ct::CachedTEX) +function _show(io, ct, x, y) if isnothing(ct.doc) - println(io, "CachedTEX(no document, $(ct.ptr), $(ct.dims))") + println(io, x, "(no document, $(ct.ptr), $(ct.dims))") elseif length(ct.doc.contents) > 1000 - println(io, "CachedTEX(TexDocument(...), $(ct.ptr), $(ct.dims))") + println(io, x, "(", y, "(...), $(ct.ptr), $(ct.dims))") else - println(io, "CachedTEX($(ct.doc), $(ct.ptr), $(ct.dims))") + println(io, x, "($(ct.doc), $(ct.ptr), $(ct.dims))") end end +Base.show(io::IO, ct::CachedTEX) = _show(io, ct, "CachedTEX", "TEXDocument") +Base.show(io::IO, ct::CachedTypst) = _show(io, ct, "CachedTypst", "TypstDocument") + function implant_math(str) return TEXDocument( """\\(\\displaystyle $str\\)""", true; @@ -517,10 +624,9 @@ function rotatedrect(rect::Rect{2, T}, angle)::Rect{2, T} where T return Rect2(rmins..., (rmaxs .- rmins)...) end -function Makie.boundingbox(cachedtex::CachedTEX, position, rotation, scale, - align) - origin = offset_from_align(align, cachedtex.dims) - box = Rect2f(Point2f(origin), Vec2f(cachedtex.dims) * scale) +function Makie.boundingbox(ct::Union{CachedTEX, CachedTypst}, position, rotation, scale, align) + origin = offset_from_align(align, ct.dims) + box = Rect2f(Point2f(origin), Vec2f(ct.dims) * scale) rect = rotatedrect(box, rotation) new_origin = Point3f(rect.origin..., 0) new_widths = Vec3f(rect.widths..., 0) @@ -528,18 +634,15 @@ function Makie.boundingbox(cachedtex::CachedTEX, position, rotation, scale, end # this method copied from Makie.jl -function Makie.boundingbox(cachedtexs::AbstractVector{CachedTEX}, positions, rotations, scale, - align) - - isempty(cachedtexs) && (return Rect3f((0, 0, 0), (0, 0, 0))) +function Makie.boundingbox(cts::AbstractVector{<:Union{CachedTEX, CachedTypst}}, positions, rotations, scale, align) + isempty(cts) && (return Rect3f((0, 0, 0), (0, 0, 0))) bb = Rect3f() - broadcast_foreach(cachedtexs, positions, rotations, scale, - align) do cachedtex, pos, rot, scl, aln + broadcast_foreach(cts, positions, rotations, scale, align) do ct, pos, rot, scl, aln if !Makie.isfinite_rect(bb) - bb = Makie.boundingbox(cachedtex, pos, rot, scl, aln) + bb = Makie.boundingbox(ct, pos, rot, scl, aln) else - bb = Makie.union(bb, Makie.boundingbox(cachedtex, pos, rot, scl, aln)) + bb = Makie.union(bb, Makie.boundingbox(ct, pos, rot, scl, aln)) end end !Makie.isfinite_rect(bb) && error("Invalid `TeX` boundingbox") diff --git a/test/runtests.jl b/test/runtests.jl index f7cbc93..87e5bdd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,6 +16,27 @@ function save_test(filename, fig; kwargs...) end +function render_texample(cached, document, url) + + fig = Figure() + + lt = LTeX(fig[1, 1], convert(CachedPDF, cached(document(read(Downloads.download(url), String), false)))) + + @test true + + resize_to_layout!(fig) + + filename = splitdir(splitext(url)[1])[2] + + save_test(joinpath(@__DIR__, "test_images", "texample", filename), fig) + + + @test true + +end + + include("tex.jl") +include("typst.jl") include("svg.jl") include("pdf.jl") diff --git a/test/tex.jl b/test/tex.jl index 87756c6..ce9f9af 100644 --- a/test/tex.jl +++ b/test/tex.jl @@ -1,29 +1,9 @@ using CairoMakie, MakieTeX using Test, Downloads -function render_texample(url) - - fig = Figure() - - lt = LTeX(fig[1, 1], CachedTeX(TeXDocument(read(Downloads.download(url), String), false))) - - @test true - - resize_to_layout!(fig) - - filename = splitdir(splitext(url)[1])[2] - - save_test(joinpath(@__DIR__, "test_images", "texample", filename), fig) - - - @test true - -end - - @testset "TeX rendering" begin - can_access_texample = try + can_access_example = try Downloads.download("https://texample.net/media/tikz/examples/TEX/rotated-triangle.tex") true catch e @@ -31,7 +11,7 @@ end @warn "Cannot access texample.net; skipping tests that require it." end - can_access_texample && @testset "texample.net" begin + can_access_example && @testset "texample.net" begin mkpath(joinpath(example_path, "texample")) @@ -49,7 +29,8 @@ end for name in names @testset "$name" begin - render_texample("https://texample.net/media/tikz/examples/TEX/$name.tex") + render_texample(CachedTeX, TeXDocument, + "https://texample.net/media/tikz/examples/TEX/$name.tex") end end @@ -251,4 +232,4 @@ end end =# -end \ No newline at end of file +end diff --git a/test/typst.jl b/test/typst.jl new file mode 100644 index 0000000..cb39caa --- /dev/null +++ b/test/typst.jl @@ -0,0 +1,37 @@ +using CairoMakie, MakieTeX +using Test, Downloads + +url(name) = "https://raw.githubusercontent.com/typst/packages/main/packages/preview/cetz/0.2.2/gallery/$name.typ" + +@testset "Typst rendering" begin + + names = [ + "karls-picture", + "tree", + "waves", + "pie-chart", + "plot", + "barchart" + ] + + can_access_example = try + _url = url(first(names)) + Downloads.download(_url) + true + catch e + false + @warn "Cannot access $_url; skipping tests that require it." + end + + can_access_example && @testset "cetz" begin + + for name in names + + @testset "$name" begin + render_texample(CachedTypst, TypstDocument, url(name)) + end + + end + + end +end