From c96c6528afe7a94bcaf4f166ff792a4b4d562988 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Sun, 30 Sep 2018 12:46:36 -0400 Subject: [PATCH 1/4] separate pango and fontconfig dependencies --- src/Compose.jl | 13 +++++++++---- src/fontconfig.jl | 30 ++++++++++++++++++++++++++++++ src/pango.jl | 40 +++++----------------------------------- 3 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 src/fontconfig.jl diff --git a/src/Compose.jl b/src/Compose.jl index d6f41fa4..0324dc98 100644 --- a/src/Compose.jl +++ b/src/Compose.jl @@ -123,9 +123,9 @@ default_fill_color = colorant"black" # Use cairo for the PNG, PS, PDF if it's installed. macro missing_cairo_error(backend) msg1 = """ - The Cairo and Fontconfig packages are necessary for the $(backend) backend. - Add them with the package manager if necessary, then run: - import Cairo, Fontconfig + The Cairo package is necessary for the $(backend) backend. + Add it with the package manager if necessary, then run: + import Cairo before invoking $(backend). """ string(msg1) @@ -139,13 +139,18 @@ PDF(args...; kwargs...) = error(@missing_cairo_error "PDF") include("svg.jl") include("pgf_backend.jl") -# If available, pango and fontconfig are used to compute text extents and match +# If imported, pango and/or fontconfig are used to compute text extents and match # fonts. Otherwise a simplistic pure-julia fallback is used. include("fontfallback.jl") function link_fontconfig() @info "Loading Fontconfig backend into Compose.jl" + include("fontconfig.jl") +end + +function link_pango() + @info "Loading Pango backend into Compose.jl" pango_cairo_ctx = C_NULL include("pango.jl") diff --git a/src/fontconfig.jl b/src/fontconfig.jl new file mode 100644 index 00000000..80ec8330 --- /dev/null +++ b/src/fontconfig.jl @@ -0,0 +1,30 @@ +# Use the freetype/fontconfig backend to find the best match to a font +# description. +# +# Args: +# desc: A string giving the font description. This can +# also provide a comma-separated list of families. E.g., +# "Helvetica, Arial 10" +# +# Returns: +# A pointer to a PangoFontDescription with the closest match. +# +let available_font_families = Set{AbstractString}() + for font_pattern in Fontconfig.list() + push!(available_font_families, lowercase(Fontconfig.format(font_pattern, "%{family}"))) + end + + meta_families = Set(["serif", "sans", "sans-serif", "monospace", "cursive", "fantasy"]) + + global match_font + function match_font(families::AbstractString, size::Float64) + matched_family = "sans-serif" + for family in [lowercase(strip(family, [' ', '"', '\''])) for family in split(families, ',')] + if family in available_font_families || family in meta_families + matched_family = family + break + end + end + Fontconfig.format(match(Fontconfig.Pattern(family=family)), "%{family}") + end +end diff --git a/src/pango.jl b/src/pango.jl index 0b3d0d46..3db0159b 100644 --- a/src/pango.jl +++ b/src/pango.jl @@ -16,40 +16,6 @@ const PANGO_SCALE = 1024.0 pango_fmt_float(x::Float64) = @sprintf("%0.4f", x) -# Use the freetype/fontconfig backend to find the best match to a font -# description. -# -# Args: -# desc: A string giving the font description. This can -# also provide a comma-separated list of families. E.g., -# "Helvetica, Arial 10" -# -# Returns: -# A pointer to a PangoFontDescription with the closest match. -# -let available_font_families = Set{AbstractString}() - for font_pattern in Fontconfig.list() - push!(available_font_families, lowercase(Fontconfig.format(font_pattern, "%{family}"))) - end - - meta_families = Set(["serif", "sans", "sans-serif", "monospace", "cursive", "fantasy"]) - - global match_font - function match_font(families::AbstractString, size::Float64) - matched_family = "sans-serif" - for family in [lowercase(strip(family, [' ', '"', '\''])) for family in split(families, ',')] - if family in available_font_families || family in meta_families - matched_family = family - break - end - end - family = Fontconfig.format(match(Fontconfig.Pattern(family=family)), "%{family}") - desc = @sprintf("%s %fpx", family, size) - fd = ccall((:pango_font_description_from_string, libpango), Ptr{Cvoid}, (Ptr{UInt8},), desc) - return fd - end -end - # Thin wrapper for a pango_layout object. mutable struct PangoLayout layout::Ptr{Cvoid} @@ -65,7 +31,11 @@ end # Set the layout's font. function pango_set_font(pangolayout::PangoLayout, family::AbstractString, pts::Number) - fd = match_font(family, pts) + family = match_font(family, pts) + + desc = @sprintf("%s %fpx", family, size) + fd = ccall((:pango_font_description_from_string, libpango), Ptr{Cvoid}, (Ptr{UInt8},), desc) + ccall((:pango_layout_set_font_description, libpango), Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), pangolayout.layout, fd) end From 81c9f5f8d1c27c0577b27ef165a985e7cb2174a0 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Sun, 30 Sep 2018 16:05:58 -0400 Subject: [PATCH 2/4] use Pango.jl instead of Cairo._jl_libpango --- src/Compose.jl | 13 +++++------ src/pango.jl | 61 +++++++++++++++++--------------------------------- 2 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/Compose.jl b/src/Compose.jl index 0324dc98..a12a8699 100644 --- a/src/Compose.jl +++ b/src/Compose.jl @@ -151,15 +151,13 @@ end function link_pango() @info "Loading Pango backend into Compose.jl" - pango_cairo_ctx = C_NULL + global pango_cairo_ctx = C_NULL include("pango.jl") - ccall((:g_type_init, libgobject), Cvoid, ()) - pango_cairo_fm = ccall((:pango_cairo_font_map_new, libpangocairo), - Ptr{Cvoid}, ()) - pango_cairo_ctx = ccall((:pango_font_map_create_context, libpango), - Ptr{Cvoid}, (Ptr{Cvoid},), pango_cairo_fm) - pangolayout = PangoLayout() + #Pango.g_type_init() + pango_cairo_fm = Pango.pango_cairo_font_map_new() + pango_cairo_ctx = Pango.pango_font_map_create_context(pango_cairo_fm) + pangolayout = Base.invokelatest(PangoLayout) end function link_cairo() @@ -171,6 +169,7 @@ end function __init__() @require Cairo="159f3aea-2a34-519c-b102-8c37f9878175" link_cairo() @require Fontconfig="186bb1d3-e1f7-5a2c-a377-96d770f13627" link_fontconfig() + @require Pango="4071c42e-c4ca-11e8-2c78-e3c1daf35efa" link_pango() end show(io::IO, m::MIME"text/html", ctx::Context) = diff --git a/src/pango.jl b/src/pango.jl index 3db0159b..7accb9b3 100644 --- a/src/pango.jl +++ b/src/pango.jl @@ -1,9 +1,5 @@ # Estimation of text extents using pango. -const libpangocairo = Cairo._jl_libpangocairo -const libpango = Cairo._jl_libpango -const libgobject = Cairo._jl_libgobject - # Cairo text backend const CAIRO_FONT_TYPE_TOY = 0 const CAIRO_FONT_TYPE_FT = 1 @@ -22,8 +18,7 @@ mutable struct PangoLayout end function PangoLayout() - layout = ccall((:pango_layout_new, libpango), - Ptr{Cvoid}, (Ptr{Cvoid},), pango_cairo_ctx) + layout = Pango.pango_layout_new(pango_cairo_ctx) # TODO: finalizer? PangoLayout(layout) @@ -34,10 +29,9 @@ function pango_set_font(pangolayout::PangoLayout, family::AbstractString, pts::N family = match_font(family, pts) desc = @sprintf("%s %fpx", family, size) - fd = ccall((:pango_font_description_from_string, libpango), Ptr{Cvoid}, (Ptr{UInt8},), desc) + fd = Pango.pango_font_description_from_string(desc) - ccall((:pango_layout_set_font_description, libpango), - Cvoid, (Ptr{Cvoid}, Ptr{Cvoid}), pangolayout.layout, fd) + Pango.pango_layout_set_font_description(pangolayout.layout, fd) end # Find the width and height of a string. @@ -51,14 +45,10 @@ end # function pango_text_extents(pangolayout::PangoLayout, text::AbstractString) textarray = convert(Vector{UInt8}, convert(String, text)) - ccall((:pango_layout_set_markup, libpango), - Cvoid, (Ptr{Cvoid}, Ptr{UInt8}, Int32), - pangolayout.layout, textarray, length(textarray)) + Pango.pango_layout_set_markup(pangolayout.layout, textarray, length(textarray)) extents = Array{Int32}(undef, 4) - ccall((:pango_layout_get_extents, libpango), - Cvoid, (Ptr{Cvoid}, Ptr{Int32}, Ptr{Int32}), - pangolayout.layout, extents, C_NULL) + Pango.pango_layout_get_extents(pangolayout.layout, extents, C_NULL) width, height = (extents[3] / PANGO_SCALE)pt, (extents[4] / PANGO_SCALE)pt end @@ -76,11 +66,11 @@ end # A (width, height) tuple in absolute units. # function max_text_extents(font_family::AbstractString, pts::Float64, texts::AbstractString...) - pango_set_font(pangolayout::PangoLayout, font_family, pts) + Pango.pango_set_font(pangolayout::PangoLayout, font_family, pts) max_width = 0mm max_height = 0mm for text in texts - (width, height) = pango_text_extents(pangolayout::PangoLayout, text) + (width, height) = Pango.pango_text_extents(pangolayout::PangoLayout, text) max_width = max_width.value < width.value ? width : max_width max_height = max_height.value < height.value ? height : max_height end @@ -96,8 +86,8 @@ end # Return an array with the extents of each element function text_extents(font_family::AbstractString, pts::Float64, texts::AbstractString...) - pango_set_font(pangolayout::PangoLayout, font_family, pts) - return [pango_text_extents(pangolayout::PangoLayout, text) for text in texts] + Pango.pango_set_font(pangolayout::PangoLayout, font_family, pts) + return [Pango.pango_text_extents(pangolayout::PangoLayout, text) for text in texts] end text_extents(font_family::AbstractString, size::Measure, texts::AbstractString...) = @@ -242,23 +232,17 @@ unpack_pango_float(ptr::Ptr{Cvoid}) = unsafe_wrap(Array, convert(Ptr{Float64}, p # should be applied starting at that position. # function unpack_pango_attr_list(ptr::Ptr{Cvoid}) - attr_it = ccall((:pango_attr_list_get_iterator, libpango), - Ptr{Cvoid}, (Ptr{Cvoid},), ptr) + attr_it = Pango.pango_attr_list_get_iterator(ptr) # Alias some ugly C calls. - attr_it_next = () -> ccall((:pango_attr_iterator_next, libpango), - Int32, (Ptr{Cvoid},), attr_it) + attr_it_next = () -> Pango.pango_attr_iterator_next(attr_it) - attr_it_get = attr_name -> ccall((:pango_attr_iterator_get, libpango), - Ptr{Cvoid}, (Ptr{Cvoid}, Int32), - attr_it, eval(attr_name)) + attr_it_get = attr_name -> Pango.pango_attr_iterator_get(attr_it, eval(attr_name)) attr_it_range = () -> begin start_idx = Array{Int32}(undef, 1) end_idx = Array{Int32}(undef, 1) - ccall((:pango_attr_iterator_range, libpango), - Cvoid, (Ptr{Cvoid}, Ptr{Int32}, Ptr{Int32}), - attr_it, start_idx, end_idx) + Pango.pango_attr_iterator_range(attr_it, start_idx, end_idx) (start_idx[1], end_idx[1]) end @@ -282,28 +266,23 @@ function unpack_pango_attr_list(ptr::Ptr{Cvoid}) push!(attrs, (start_idx, attr)) end - ccall((:pango_attr_iterator_destroy, libpango), - Cvoid, (Ptr{Cvoid},), attr_it) + Pango.pango_attr_iterator_destroy(attr_it) attrs end function pango_to_svg(text::AbstractString) - c_stripped_text = Ref{Ptr{UInt8}}() + c_stripped_text = Ref{Cstring}() c_attr_list = Ref{Ptr{Cvoid}}() - ret = ccall((:pango_parse_markup, libpango), - Int32, (Cstring, Int32, UInt32, Ptr{Ptr{Cvoid}}, - Ptr{Ptr{UInt8}}, Ptr{UInt32}, Ptr{Cvoid}), - text, -1, 0, c_attr_list, c_stripped_text, - C_NULL, C_NULL) + ret = Pango.pango_parse_markup(text, -1, 0, c_attr_list, c_stripped_text, C_NULL, C_NULL) ret == 0 && error("Could not parse pango markup.") # TODO: do c_stripped_text and c_attr_list need to be freed? - text = unsafe_wrap(Array, c_stripped_text[]) + text = unsafe_string(c_stripped_text[]) last_idx = 1 open_tag = false @@ -333,16 +312,16 @@ function pango_to_svg(text::AbstractString) if !(attr.rise === nothing) bs = -((attr.rise / PANGO_SCALE)pt).value - @printf(io, " dy=\"%s\"", pango_fmt_float(bs)) + @printf(io, " dy=\"%s\"", Pango.pango_fmt_float(bs)) baseline_shift = bs elseif baseline_shift != 0.0 - @printf(io, " dy=\"%s\"", pango_fmt_float(-baseline_shift)) + @printf(io, " dy=\"%s\"", Pango.pango_fmt_float(-baseline_shift)) baseline_shift = 0.0 end if !(attr.scale === nothing) @printf(io, " font-size=\"%s%%\"", - pango_fmt_float(100.0 * attr.scale)) + Pango.pango_fmt_float(100.0 * attr.scale)) baseline_shift *= attr.scale end From ddaff21d454a1cde3f8a3d898b3e635bc4d700c8 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Sun, 23 Sep 2018 18:22:40 -0400 Subject: [PATCH 3/4] abandon current_point() --- src/abandoned.jl | 8 +++++++- src/cairo_backends.jl | 8 -------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/abandoned.jl b/src/abandoned.jl index 21af47a4..2f7813e8 100644 --- a/src/abandoned.jl +++ b/src/abandoned.jl @@ -812,5 +812,11 @@ function draw(img::SVG, prim::PathPrimitive, idx::Int) print(img.out, "/>\n") end - +function current_point(img::Image) + x = Array{Float64}(undef, 1) + y = Array{Float64}(undef, 1) + ccall((:cairo_get_current_point, Cairo._jl_libcairo), Cvoid, + (Ptr{Cvoid}, Ptr{Float64}, Ptr{Float64}), img.ctx.ptr, x, y) + return ((x[1] / img.ppmm)*mm, (x[2] / img.ppmm)*mm) +end diff --git a/src/cairo_backends.jl b/src/cairo_backends.jl index de221682..9aa1b494 100644 --- a/src/cairo_backends.jl +++ b/src/cairo_backends.jl @@ -457,14 +457,6 @@ apply_property(img::Image, property::SVGAttributePrimitive) = nothing # Cairo Wrappers # -------------- -function current_point(img::Image) - x = Array{Float64}(undef, 1) - y = Array{Float64}(undef, 1) - ccall((:cairo_get_current_point, Cairo._jl_libcairo), Cvoid, - (Ptr{Cvoid}, Ptr{Float64}, Ptr{Float64}), img.ctx.ptr, x, y) - return ((x[1] / img.ppmm)*mm, (x[2] / img.ppmm)*mm) -end - move_to(img::Image, point::AbsoluteVec2) = Cairo.move_to(img.ctx, absolute_native_units(img, point[1].value), absolute_native_units(img, point[2].value)) From 92e595c6be46a920ed714768d1c7471b0ff1040e Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Sun, 30 Sep 2018 16:54:59 -0400 Subject: [PATCH 4/4] document Cairo, Pango, and Fontconfig dependencies --- docs/src/index.md | 4 ++-- docs/src/tutorial.md | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 66d4f667..8b4306f6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -11,8 +11,8 @@ to simplify the creation of complex graphics and serves as the basis of the ## Package features -- Renders publication quality graphics to SVG, PNG, Postscript, PDF and PGF -- Intuitive and consistent interface +- Renders publication quality graphics to PGF and interactive SVG using pure Julia; relies on [cairo](https://cairographics.org/) for PNG, Postscript, PDF and PGF +- Can handle most text formatting natively; calls out to [pango](https://www.pango.org/) for more advanced notation and [fontconfig](https://www.fontconfig.org/) for a wider choice of fonts - Works with [Jupyter](http://jupyter.org/) notebooks via [IJulia](https://github.com/JuliaLang/IJulia.jl) out of the box ## Installation diff --git a/docs/src/tutorial.md b/docs/src/tutorial.md index 8a281fdc..bd7be7ee 100644 --- a/docs/src/tutorial.md +++ b/docs/src/tutorial.md @@ -58,6 +58,15 @@ composition2 |> SVG("celery.svg") composition3 |> SVG("rutabaga.svg") # etc... ``` +To render to PDF, PNG, and postscript, first install, if necessary, +and load Cairo before instantiating a backend: + +``` +Pkg.add("Cairo") +import Cairo +composition |> PDF("tomato.pdf") +``` + ## The compose function accepts S-expressions