@@ -480,57 +480,119 @@ Base.@kwdef struct TableLevelCellMargins
480480 stop:: Maybe{Twip} = nothing
481481end
482482
483+ struct Path
484+ path:: String
485+ end
486+
487+ """
488+ Image(m::MIME, object)
489+
490+ Represents an image of MIME type `m` that can be written to an appropriate file
491+ when writing out a docx document. The `object` needs to have a `show` method
492+ for `m` defined for the default behavior to work.
493+ """
494+ struct Image{MIME,T}
495+ m:: MIME
496+ object:: T
497+ end
498+
499+ """
500+ Image(path::String)
501+
502+ Create an `Image` pointing to the file at `path`. The MIME type is determined by file extension.
503+ """
504+ Image(path:: String ) = Image(Path(path))
505+ Image(i:: Image ) = i
506+
507+ function Image(path:: Path )
508+ if endswith(path. path, r" \. svg" i )
509+ Image(MIME" image/svg+xml" (), path)
510+ elseif endswith(path. path, r" \. png" i )
511+ Image(MIME" image/png" (), path)
512+ else
513+ throw(ArgumentError(" Unknown image file extension, only .svg and .png are allowed. Path was \" $(path. path) \" " ))
514+ end
515+ end
516+
517+ # avoid writing file again which is already given as a path
518+ function get_image_file(i:: Image{M,Path} , tempdir) where M
519+ return i. object. path
520+ end
521+
522+ file_extension(:: Type{MIME"image/svg+xml"} ) = " svg"
523+ file_extension(:: Type{MIME"image/png"} ) = " png"
524+
525+ function get_image_file(i:: Image{M} , tempdir) where M
526+ ext = file_extension(M)
527+ filepath = joinpath(tempdir, " temp.$(ext) " )
528+ open(filepath, " w" ) do io
529+ Base. show(io:: IO , M(), i. object)
530+ end
531+ return filepath
532+ end
533+
483534"""
484535 InlineDrawing{T}(; image::T, width::EMU, height::EMU)
485536
486537Create an `InlineDrawing` object which, as the name implies, can be placed inline with
487538text inside [`Run`](@ref)s.
488539
489540WriteDocx supports different types `T` for the `image` argument.
490- If `T` is a `String`, `image` is treated as the file path to a .png or .svg image.
541+ If `T` is a `String`, `image` is treated as the file path to an existing .png or .svg image.
542+ You can pass an `Image` object which can hold a reference to an object that can be written
543+ to a file with the desired MIME type at render time.
491544You can also use [`SVGWithPNGFallback`](@ref) to place .svg images with better fallback behavior.
492545
493546Width and height of the placed image are set via `width` and `height`, note that you have to
494547determine these values yourself for any image you place, a correct aspect ratio will not
495548be determined automatically.
496549"""
497- Base . @kwdef struct InlineDrawing{T}
550+ struct InlineDrawing{T}
498551 image:: T
499552 width:: EMU
500553 height:: EMU
501554end
502555
556+ function InlineDrawing(; image, width, height)
557+ InlineDrawing(image, width, height)
558+ end
559+
560+ InlineDrawing(image:: T , width, height) where T = InlineDrawing{T}(image, convert(EMU, width), convert(EMU, height))
561+
562+ # backwards-compatibility, interpret String as path to an image
563+ function InlineDrawing(path:: AbstractString , width, height)
564+ InlineDrawing(Image(path), width, height)
565+ end
566+
567+ # fix ambiguity
568+ function InlineDrawing(path:: AbstractString , width:: EMU , height:: EMU )
569+ InlineDrawing(Image(path), width, height)
570+ end
571+
503572is_inline_element(:: Type{<:InlineDrawing} ) = true
504573
505574"""
506- SVGWithPNGFallback(; svg::String , png::String )
575+ SVGWithPNGFallback(; svg, png)
507576
508- Create a `SVGWithPNGFallback` for the svg file at path `svg` and the fallback png
509- file at path `png`.
577+ Create a `SVGWithPNGFallback` for the svg `svg` and the fallback `png`.
578+ If `svg` or `png` are `AbstractString`s, they will be treated as paths to image files.
579+ Otherwise, they should be `Image`s with the appropriate MIME types. Use `SVGImage` and
580+ `PNGImage` as shortcuts to create these.
510581
511582Word Online and other services like Slack preview don't work when a simple svg file is added via
512583[`InlineDrawing`](@ref)`{String}`.
513584`SVGWithPNGFallback` supplies a fallback png file which will be used for display in those situations.
514585Note that it is your responsibility to check whether the png file is an accurate
515586replacement for the svg.
516587"""
517- struct SVGWithPNGFallback
518- svg:: String # path to SVG
519- png:: String # path to PNG
520- function SVGWithPNGFallback(; svg:: AbstractString , png:: AbstractString )
521- svg = convert(String, svg)
522- png = convert(String, png)
523- if ! endswith(svg, " .svg" )
524- throw(ArgumentError(" SVG file needs to end with .svg, got $svg " ))
525- end
526- if ! endswith(png, " .png" )
527- throw(ArgumentError(" PNG file needs to end with .png, got $png " ))
528- end
529- new(svg, png)
530- end
588+ struct SVGWithPNGFallback{T1,T2}
589+ svg:: Image{MIME"image/svg+xml",T1} # path to SVG
590+ png:: Image{MIME"image/png",T2} # path to PNG
531591end
532592
533- InlineDrawing(image:: T , width, height) where T = InlineDrawing{T}(image, convert(EMU, width), convert(EMU, height))
593+ function SVGWithPNGFallback(; svg, png)
594+ SVGWithPNGFallback(Image(svg), Image(png))
595+ end
534596
535597@partialkw struct Style{T}
536598 id:: String
@@ -1303,10 +1365,6 @@ struct Relationship
13031365 target:: String
13041366end
13051367
1306- struct ImageMedium{T}
1307- image:: T
1308- end
1309-
13101368struct StyleRel end
13111369
13121370function resolve_rel!(x:: StyleRel , i, dir, prefix)
@@ -1316,16 +1374,18 @@ function resolve_rel!(x::StyleRel, i, dir, prefix)
13161374 )
13171375end
13181376
1319- function resolve_rel!(x:: ImageMedium{String} , i, dir, prefix)
1320- imgpath = x. image
1321- mediapath = joinpath(dir, " word" , " media" )
1322- mkpath(mediapath)
1323- _, extension = splitext(imgpath)
1324- targetpath = joinpath(mediapath, " $(prefix) image_$i " * extension)
1325- cp(imgpath, targetpath)
1326- type = " http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
1327- target = relpath(targetpath, joinpath(dir, " word" ))
1328- return Relationship(type, target)
1377+ function resolve_rel!(x:: Image , i, dir, prefix)
1378+ mktempdir() do tempdir
1379+ imgpath = get_image_file(x, tempdir)
1380+ mediapath = joinpath(dir, " word" , " media" )
1381+ mkpath(mediapath)
1382+ _, extension = splitext(imgpath)
1383+ targetpath = joinpath(mediapath, " $(prefix) image_$i " * extension)
1384+ cp(imgpath, targetpath)
1385+ type = " http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
1386+ target = relpath(targetpath, joinpath(dir, " word" ))
1387+ return Relationship(type, target)
1388+ end
13291389end
13301390
13311391function resolve_rel!(h:: Union{Header, Footer} , i, dir, prefix)
@@ -1369,14 +1429,14 @@ end
13691429
13701430gather_rels!(rels, zipdir, :: Nothing ) = nothing
13711431
1372- function gather_rels!(rels, zipdir, i:: InlineDrawing{String } )
1373- add_rel!(rels, ImageMedium( i. image) )
1432+ function gather_rels!(rels, zipdir, i:: InlineDrawing{<:Image } )
1433+ add_rel!(rels, i. image)
13741434 return
13751435end
13761436
1377- function gather_rels!(rels, zipdir, i:: InlineDrawing{SVGWithPNGFallback} )
1378- add_rel!(rels, ImageMedium( i. image. png) )
1379- add_rel!(rels, ImageMedium( i. image. svg) )
1437+ function gather_rels!(rels, zipdir, i:: InlineDrawing{<: SVGWithPNGFallback} )
1438+ add_rel!(rels, i. image. png)
1439+ add_rel!(rels, i. image. svg)
13801440 return
13811441end
13821442
@@ -1637,9 +1697,9 @@ xml(name, pairs::Pair{String,<:Any}...) = xml(name, [], pairs...)
16371697
16381698to_xml(xml:: E.Node , _) = xml
16391699
1640- function get_ablip(i:: InlineDrawing{String} , rels)
1641- index = rels[ImageMedium( i. image) ]
1642- ablip = if endswith(i . image, r" \. svg" i )
1700+ function get_ablip(i:: InlineDrawing{<:Image{M}} , rels) where M
1701+ index = rels[i. image]
1702+ ablip = if M <: MIME"image/ svg+xml"
16431703 xml(" a:blip" , [
16441704 xml(" a:extLst" , [
16451705 # this is the empty scaffolding of a png thumbnail that doesn't actually need to be there in modern Word as it seems
@@ -1650,17 +1710,17 @@ function get_ablip(i::InlineDrawing{String}, rels)
16501710 ], " uri" => " {96DAC541-7B7A-43D3-8B79-37D633B846F1}" )
16511711 ])
16521712 ])
1653- elseif endswith(i . image, r" \. png" i )
1713+ elseif M <: MIME"image/ png"
16541714 xml(" a:blip" , " r:embed" => " rId$index " )
16551715 else
1656- error(" Cannot deal with image that is not a .svg or .png. " )
1716+ error(" Cannot deal with image of MIME type $M " )
16571717 end
16581718 return (; ablip, index)
16591719end
16601720
1661- function get_ablip(i:: InlineDrawing{SVGWithPNGFallback} , rels)
1662- index_png = rels[ImageMedium( i. image. png) ]
1663- index_svg = rels[ImageMedium( i. image. svg) ]
1721+ function get_ablip(i:: InlineDrawing{<: SVGWithPNGFallback} , rels)
1722+ index_png = rels[i. image. png]
1723+ index_svg = rels[i. image. svg]
16641724 ablip =
16651725 xml(" a:blip" , [
16661726 xml(" a:extLst" , [
0 commit comments