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

Enable hyperlinks #54

Merged
merged 2 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ my_table = Table(df; offset_x=60, offset_y=80, size_x=150, size_y=40)
push!(s4, my_table)
push!(pres, s4)

# and what about a nice link in slide 2 to the table-slide
text = TextBox(; content="Click here to see a nice table", offset_x=100, offset_y=140, size_x=150, size_y=20, hlink = s4)
push!(s2, text)

pres

# output
Expand Down
5 changes: 4 additions & 1 deletion src/AbstractShape.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ end

function _show_string(shape::AbstractShape, compact::Bool)
return "$(typeof(shape))"
end
end

hlink_xml(hlink) = Dict("a:hlinkClick" => Dict("rId" => "rId$(rid(hlink))", "action" => "ppaction://hlinksldjump"))
has_hyperlink(s::AbstractShape) = hasfield(typeof(s), :hlink) && !isnothing(s.hlink)
11 changes: 10 additions & 1 deletion src/Presentation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct Presentation
function Presentation(slides::Vector{Slide}, author::String, title::String)
pres = new(title, author, Slide[], PresentationState())
if isempty(slides)
slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT)]
slides = [Slide(; title=title, layout=TITLE_SLIDE_LAYOUT, slide_nr = 1)]
end
for slide in slides
push!(pres, slide)
Expand All @@ -68,6 +68,7 @@ end

function Base.push!(pres::Presentation, slide::Slide)
slide.rid = new_rid(pres)
slide.slide_nr = length(slides(pres)) + 1
return push!(slides(pres), slide)
end

Expand Down Expand Up @@ -184,4 +185,12 @@ function update_presentation_state!(p::Presentation, template::ZipBufferReader)
sz = PresentationSize(parse(Int, cx), parse(Int, cy))
p._state.size = sz
return nothing
end

# the slide filename (e.g. slide2.xml) can be used in the slide relationships to enable hyperlinks for example
# We need to make sure that slide N is also really written to slideN.xml therefore slide numbers are updated just before writing
function update_slide_nrs!(p::Presentation)
for (index, slide) in enumerate(slides(p))
slide.slide_nr = index
end
end
28 changes: 25 additions & 3 deletions src/Slide.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ mutable struct Slide
shapes::Vector{AbstractShape}
rid::Int
layout::Int
slide_nr::Int
function Slide(
title::String,
shapes::Vector{<:AbstractShape},
rid::Int=0,
layout::Int=DEFAULT_SLIDE_LAYOUT,
slide_nr::Int=0
)
slide = new(title, AbstractShape[], rid, layout)
slide = new(title, AbstractShape[], rid, layout, slide_nr)
for shape in shapes
push!(slide, shape)
end
Expand All @@ -57,16 +59,17 @@ mutable struct Slide
end
shapes(s::Slide) = s.shapes
rid(s::Slide) = s.rid

slide_nr(s::Slide) = s.slide_nr
Slide(shapes::Vector{<:AbstractShape}; kwargs...) = Slide(; shapes=shapes, kwargs...)

function Slide(;
title::String="",
shapes::Vector{<:AbstractShape}=AbstractShape[],
rid::Int=0,
layout::Int=DEFAULT_SLIDE_LAYOUT,
slide_nr::Int=0
)
return Slide(title, shapes, rid, layout)
return Slide(title, shapes, rid, layout, slide_nr)
end

function new_rid(slide::Slide)
Expand All @@ -77,6 +80,8 @@ function new_rid(slide::Slide)
end
end

slide_fname(s::Slide) = "slide$(s.slide_nr).xml"

function Base.push!(slide::Slide, shape::AbstractShape)
if has_rid(shape)
push!(shapes(slide), set_rid(shape, new_rid(slide)))
Expand Down Expand Up @@ -138,6 +143,20 @@ function init_sptree()::AbstractDict
)
end

function type_schema(s::Slide)
return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
end

function relationship_xml(s::Slide)
return Dict(
"Relationship" => [
Dict("Id" => "rId$(rid(s))"),
Dict("Type" => type_schema(s)),
Dict("Target" => slide_fname(s)),
],
)
end

function make_slide_relationships(s::Slide)::AbstractDict
xml_slide_rels = OrderedDict("Relationships" => Dict[])
push!(
Expand All @@ -160,6 +179,9 @@ function make_slide_relationships(s::Slide)::AbstractDict
if has_rid(shape)
push!(xml_slide_rels["Relationships"], relationship_xml(shape))
end
if has_hyperlink(shape)
push!(xml_slide_rels["Relationships"], relationship_xml(shape.hlink))
end
end
return xml_slide_rels
end
11 changes: 10 additions & 1 deletion src/TextBox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ struct TextBox <: AbstractShape
offset_y::Int # EMUs
size_x::Int # EMUs
size_y::Int # EMUs
hlink::Union{Nothing, Any}
function TextBox(
content::AbstractString,
offset_x::Real, # millimeters
offset_y::Real, # millimeters
size_x::Real, # millimeters
size_y::Real, # millimeters
style::Dict=Dict("bold" => false, "italic" => false),
hlink::Union{Nothing, Any} = nothing
)
# input is in mm
return new(
Expand All @@ -78,6 +80,7 @@ struct TextBox <: AbstractShape
Int(round(offset_y * _EMUS_PER_MM)),
Int(round(size_x * _EMUS_PER_MM)),
Int(round(size_y * _EMUS_PER_MM)),
hlink
)
end
end
Expand All @@ -90,6 +93,7 @@ function TextBox(;
size_x::Real=40, # millimeters
size_y::Real=30, # millimeters
style::Dict=Dict("bold" => false, "italic" => false),
hlink::Union{Nothing, Any} = nothing
)
return TextBox(
content,
Expand All @@ -98,6 +102,7 @@ function TextBox(;
size_x,
size_y,
style,
hlink
)
end

Expand Down Expand Up @@ -130,7 +135,11 @@ function text_style_xml(t::TextBody)
end

function make_xml(t::TextBox, id::Int=1)
cNvPr = Dict("p:cNvPr" => [Dict("id" => "$id"), Dict("name" => "TextBox")])
cNvPr = Dict("p:cNvPr" => Dict[Dict("id" => "$id"), Dict("name" => "TextBox")])
if has_hyperlink(t)
push!(cNvPr["p:cNvPr"], hlink_xml(t.hlink))
end

cNvSpPr = Dict("p:cNvSpPr" => Dict("txBox" => "1"))
nvPr = Dict("p:nvPr" => missing)

Expand Down
1 change: 1 addition & 0 deletions src/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ function Base.write(
mktemp(filedir) do temp_path, temp_out
ZipWriter(temp_out; own_io=true) do w
update_presentation_state!(p, template_reader)
update_slide_nrs!(p)
write_relationships!(w, p)
write_presentation!(w, p)
write_slides!(w, p, template_reader)
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ end

include("testConstructors.jl")
include("testTables.jl")
include("testHyperlinks.jl")
include("testSlideXML.jl")
include("testWriting.jl")
44 changes: 44 additions & 0 deletions test/testHyperlinks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

@testset "Test Hyperlinks" begin
pres = Presentation()
s2 = Slide()
s3 = Slide()

box = TextBox(content = "Hyperlinked text", hlink = s3)

@testset "has hyperlink" begin
@test PPTX.has_hyperlink(box)
box_nolink = TextBox(content = "Hyperlinked text")
@test !PPTX.has_hyperlink(box_nolink)
end

@testset "hyperlink xml" begin
xml = PPTX.hlink_xml(box.hlink)
@test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))"
end

push!(s2, box)
push!(pres, s2)
push!(pres, s3)

@testset "hyperlink xml after rid update" begin
# After updating the rId of s2 the hyperlink xml should also be updated
xml = PPTX.hlink_xml(box.hlink)
@test xml["a:hlinkClick"]["rId"] == "rId$(PPTX.rid(s3))"
end

# I malificently swap slides which should not affect hyperlinking
pres.slides[2] = s3
pres.slides[3] = s2

@testset "update slide nrs" begin
PPTX.update_slide_nrs!(pres)
@test pres.slides[2].slide_nr == 2
@test pres.slides[3].slide_nr == 3
xml_rels = PPTX.relationship_xml(box.hlink)
# Chech that rId is still the same
@test xml_rels["Relationship"][1]["Id"] == "rId$(PPTX.rid(s3))"
@test xml_rels["Relationship"][3]["Target"] == "slide$(PPTX.slide_nr(s3)).xml"
end

end
Loading