svglite/0000755000176200001440000000000014534641075011740 5ustar liggesuserssvglite/NAMESPACE0000644000176200001440000000101314006257645013152 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(print,font_face) S3method(print,svg) export(create_svgz) export(editSVG) export(font_face) export(font_feature) export(htmlSVG) export(register_font) export(register_variant) export(stringSVG) export(svglite) export(svgstring) export(xmlSVG) importFrom(systemfonts,font_feature) importFrom(systemfonts,font_info) importFrom(systemfonts,match_font) importFrom(systemfonts,register_font) importFrom(systemfonts,register_variant) useDynLib(svglite, .registration = TRUE) svglite/tools/0000755000176200001440000000000014534342073013074 5ustar liggesuserssvglite/tools/winlibs.R0000644000176200001440000000057014155671704014675 0ustar liggesusersVERSION <- commandArgs(TRUE) if(!file.exists(sprintf("../windows/harfbuzz-%s/include/png.h", VERSION))){ if(getRversion() < "3.3.0") setInternet2() download.file(sprintf("https://github.com/rwinlib/harfbuzz/archive/v%s.zip", VERSION), "lib.zip", quiet = TRUE) dir.create("../windows", showWarnings = FALSE) unzip("lib.zip", exdir = "../windows") unlink("lib.zip") } svglite/README.md0000644000176200001440000001011014511445574013211 0ustar liggesusers # svglite [![R-CMD-check](https://github.com/r-lib/svglite/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/svglite/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/r-lib/svglite/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/svglite?branch=main) [![CRAN Status Badge](http://www.r-pkg.org/badges/version/svglite)](https://cran.r-project.org/package=svglite) svglite is a graphics device that produces clean svg output, suitable for use on the web, or hand editing. Compared to the built-in `svg()`, svglite produces smaller files, and leaves text as is, making it easier to edit the result after creation. It also supports multiple nice features such as embedding of web fonts. ## Installation svglite is available on CRAN using `install.packages("svglite")`. You can install the development version from github with: ``` r # install.packages("pak") pak::pak("r-lib/svglite") ``` ## Motivation The grDevices package bundled with R already comes with an SVG device (using the eponymous `svg()` call). The development of svglite is motivated by the following considerations: ### Speed `svglite()` is considerably faster than `svg()`. If you are rendering SVGs dynamically to serve over the web this can be quite important: ``` r library(svglite) x <- runif(1e3) y <- runif(1e3) tmp1 <- tempfile() tmp2 <- tempfile() svglite_test <- function() { svglite(tmp1) plot(x, y) dev.off() } svg_test <- function() { svg(tmp2, onefile = TRUE) plot(x, y) dev.off() } bench::mark(svglite_test(), svg_test(), min_iterations = 250, check = FALSE) #> # A tibble: 2 × 6 #> expression min median `itr/sec` mem_alloc `gc/sec` #> #> 1 svglite_test() 2.08ms 2.23ms 438. 691KB 7.13 #> 2 svg_test() 6.07ms 6.26ms 159. 179KB 0.638 ``` ### File size Another point with high relevance when serving SVGs over the web is the size. `svglite()` produces much smaller files ``` r # svglite fs::file_size(tmp1) #> 75K # svg fs::file_size(tmp2) #> 321K ``` In both cases, compressing to make `.svgz` (gzipped svg) is worthwhile. svglite supports compressed output directly which will be triggered if the provided path has a `".svgz"` (or `".svg.gz"`) extension. ``` r tmp3 <- tempfile(fileext = ".svgz") svglite(tmp3) plot(x, y) invisible(dev.off()) # svglite - svgz fs::file_size(tmp3) #> 9.42K ``` ### Editability One of the main reasons for the size difference between the size of the output of `svglite()` and `svg()` is the fact that `svglite()` encodes text as styled `` elements, whereas `svg()` converts the glyphs to polygons and renders these. The latter approach means that the output of `svg()` does not require the font to be present on the system that displays the SVG but makes it more or less impossible to edit the text after the fact. svglite focuses on providing maximal editability of the output, so that you can open up the result in a vector drawing program such as Inkscape or Illustrator and polish the output if you so choose. ### Font support svglite uses systemfonts for font discovery which means that all installed fonts on your system is available to use. The systemfonts foundation means that fonts registered with `register_font()` or `register_variant()` will also be available. If any of these contains non-standard weights or OpenType features (e.g. ligatures or tabular numerics) this will be correctly encoded in the style block. systemfonts also allows you to embed webfont `@imports` in your file to ensure that the file looks as expected even on systems without the used font installed. ## Code of Conduct Please note that the svglite project is released with a [Contributor Code of Conduct](https://svglite.r-lib.org/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. svglite/man/0000755000176200001440000000000014534576401012513 5ustar liggesuserssvglite/man/svgstring.Rd0000644000176200001440000000717314511445621015032 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/SVG.R \name{svgstring} \alias{svgstring} \title{Access current SVG as a string.} \usage{ svgstring( width = 10, height = 8, bg = "white", pointsize = 12, standalone = TRUE, system_fonts = list(), user_fonts = list(), web_fonts = list(), id = NULL, fix_text_size = TRUE, scaling = 1 ) } \arguments{ \item{height, width}{Height and width in inches.} \item{bg}{Default background color for the plot (defaults to "white").} \item{pointsize}{Default point size.} \item{standalone}{Produce a standalone svg file? If \code{FALSE}, omits xml header and default namespace.} \item{system_fonts}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} \emph{Consider using \code{\link[systemfonts:register_font]{systemfonts::register_font()}} instead}. Named list of font names to be aliased with fonts installed on your system. If unspecified, the R default families \code{sans}, \code{serif}, \code{mono} and \code{symbol} are aliased to the family returned by \code{\link[systemfonts]{font_info}()}.} \item{user_fonts}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} \emph{Consider using \code{\link[systemfonts:register_font]{systemfonts::register_font()}} instead}. Named list of fonts to be aliased with font files provided by the user rather than fonts properly installed on the system. The aliases can be fonts from the fontquiver package, strings containing a path to a font file, or a list containing \code{name} and \code{file} elements with \code{name} indicating the font alias in the SVG output and \code{file} the path to a font file.} \item{web_fonts}{A list containing web fonts to use in the SVG. The fonts will still need to be available locally on the computer running the code, but viewers of the final SVG will not need the font if specified as a web font. Web fonts can either be specified using \code{\link[=font_face]{font_face()}} or given as a single string in which case they are taken to be URL's for an \verb{@import} directive to e.g. Google Fonts.} \item{id}{A character vector of ids to assign to the generated SVG's. If creating more SVG files than supplied ids the exceeding SVG's will not have an id tag and a warning will be thrown.} \item{fix_text_size}{Should the width of strings be fixed so that it doesn't change between svg renderers depending on their font rendering? Defaults to \code{TRUE}. If \code{TRUE} each string will have the \code{textLength} CSS property set to the width calculated by systemfonts and \code{lengthAdjust='spacingAndGlyphs'}. Setting this to \code{FALSE} can be beneficial for heavy post-processing that may change content or style of strings, but may lead to inconsistencies between strings and graphic elements that depend on the dimensions of the string (e.g. label borders and background).} \item{scaling}{A scaling factor to apply to the rendered line width and text size. Useful for getting the right sizing at the dimension that you need.} } \value{ A function with no arguments: call the function to get the current value of the string. } \description{ This is a variation on \code{\link{svglite}} that makes it easy to access the current value as a string. } \details{ See \code{\link{svglite}()} documentation for information about specifying fonts. } \examples{ s <- svgstring() s() plot.new() s() text(0.5, 0.5, "Hi!") s() dev.off() s <- svgstring() plot(rnorm(5), rnorm(5)) s() dev.off() } svglite/man/xmlSVG.Rd0000644000176200001440000000140114357213724014155 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/inlineSVG.R \name{xmlSVG} \alias{xmlSVG} \title{Run plotting code and return svg} \usage{ xmlSVG(code, ..., standalone = FALSE, height = 7, width = 7) } \arguments{ \item{code}{Plotting code to execute.} \item{...}{Other arguments passed on to \code{\link{svglite}}.} \item{standalone}{Produce a standalone svg file? If \code{FALSE}, omits xml header and default namespace.} \item{height, width}{Height and width in inches.} } \value{ A \code{xml2::xml_document} object. } \description{ This is useful primarily for testing. Requires the \code{xml2} package. } \examples{ if (require("xml2")) { x <- xmlSVG(plot(1, axes = FALSE)) x xml_find_all(x, ".//text") } } \keyword{internal} svglite/man/figures/0000755000176200001440000000000014534576401014157 5ustar liggesuserssvglite/man/figures/README-unnamed-chunk-3-1.png0000644000176200001440000006717714006275101020660 0ustar liggesusersPNG  IHDRz4iCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i_@IDATxǟW" b!*XPc{&[4Ďbl *t{9ݙwL_ @ %ղt@ Gʉ@ UЬ` @P@ @f7 @@r@ d@K`ҥ bEZL#nmذ!2ǐ!C駟7Zf+gXqT^~}H~!kGOf͚o9k v>[TTr[֬q@e6mj6m˗g8f>n8߿ 0}~Vn]݃6|qƍ۲eyU&M~a)QFrJvgQmֶd^+DS%#EvGgaӧO{,vp0@2Oy?}6h ӛRǎm=Gy֭[WI @ JQVLm}饗\u{޽7+]/r~@ mh/ֿ+&"M6֭[7{g @F܁Q7ҤIX^{o;vM2[' @'n^mvI"a'A x16luԩ GW^']B@G=b ?|pγBn6w\o*ڌu D4"&LpC,uڵ#fSLI$@O}F#Gjժ6lSa46.boV۱ h@FOr֡C_Sm͛g~m,YP(@@>@擷CVVE@;u*Uׯ_|M_۳ @% obmYaa-XU5;YA @ _ @!+EvuW3g~ @ !uL7nEڽ{wW oއ !@h|Ik[`UkfW_}6mw7 @ d!sH#Zw^{e-2MI @ [^|͟??%i;7on=\`< @ @9*ɓݑ۶m[e lw}׉*g 4.?&Md[N쳏=O>@@n @s?/.jJo߾ŋSʃ @h%@۴iV9+..[o5| @fyQ45jdGy$; @!' ff2ZzuPq߿ip38Æ 7T [غ6av@J,:2]pn8;:,1bO܎ 4\5իWwcyQ:u_;~K/=y@ 52'YB\SL-[aݨ+Uf <&Nh_w|bl@@ @3-&%;찃裏8tE99@ CIF~ >K%)*z}٬Y*ۜ d4P-+VXV#0 Zmex 8UNrɖ[魷޲J@ AEE`ڴin;MÙy㕞{l@P|X ę4 YiӦ4h`z_~9[8@[nņ RLgljl޽{?xlܸuz_W +мrwn + ڣGU :48: `Çvc&L+WBC|L`ɒ%0ڵkgoW_}e+8 0x!l9s=vƎk?sbCY$w߹Ii5jd_|E-P@X @≘E*i&-1?(^ H֨Q&s߆h2BdZ:լY/.hq @ '$B4+b4"J*YPP3sV H@nhĩx}xQ!@ }~XU͛7iUݧ*իW|%0o!~ב+~Æ VXXC8   P!Fa&W! ;"Jg h89E SZjyǏk,yE@yr @+t h% H^Y$@5% vԠA22J-5hݛ…eDfbFD(|@ eQFIjxhR4,@l @cpl̙ΐE@eԤIm2K`ʕQIsɬ4T1IîE۶myI@ h N &=چz&݀@ @cp(L<2uqmR>!YjTmܸ7V; &0y#(ڬYЕLQPh܂A1%E@˫ QTW)&-M߁cTlӦ} <Z^IC5 ?礔z(Y!Hjx(xTPԪU+TirI@~@懟sR0@H*M<[' !z-/Cx@ @C율 0F@[li5jԠ>'Ghy?U詑;1@L`b-ի'S7pT34լY, P%E@7l`˖-h3A1!#V ̀*jժ:" ك=q"J~[h>H@ @c\OT˕mǕe(D"|@S2ڨQ#@A wqn !<*jEEE">! zqxtJVD. B^ u٬YE|BUT!@OV1heT*x%|a{ Rfob# %98z笭A=eo@t?[iʒځ!x1TzG!ZPPz )11"gzD@=|B oNJJQNLavA !uLRz~&q۶mMQ{"*ʆ`(!y4@ @ߜNb.*Oݸq)'8(bL@3OjذIz$A&sR:Uk˨$ PI&Ed@dT ^# b(R&M;G jPUZGY h.xP]JM7{B *QTDTu[T`JD((4! +Wa P"~h Mmzu@RLk׮?>q!Ͼ#jD4d@AQ3/jC0y|;tڝ1GOO`ժUVTT[VZѓBOzE@Ԑ+ 4JDAA ~WLH~ቀv.ht}J%@V.6v6a„Pr(D'@vBRU O(z!P5ЪbJL6-ZjF;JjTz+U5JS*fsD4N Q)eǎoI\w@ j*4+"BGE%Kܢ4 R"m,X` .L\w@ E^|U"T 1Ј9,*UکS'g?QPH@P'\\\/hx}9ˢ:Z{̘1">!4h 5jX͚5}nZt}h|Z%@%4 ST6lcF P4`jCK_fdQڵ=֮]-HڀVMljM:A "qTT4c&~mÆ N&.; PuVE@ Q"Bnk"I/21'hU74BC=-!V^^A4 o~ϼE|B)H ^נD(4E@FQa7߲e˰Z}; Z駟Uj-+!yȻ0JQo2n _?I@Rס !9+̦*ڸqcSNe[&ML1 :l$'q@T91Rr,@ @E43 %ö'|bKdDk_|1ؗP K`ժU)*|E7hM:5Vtv3<3IjxN8N=T1b}v衇ڛot{B lڴɍj팡Fo@y-<`?]}ն뮻j~[ؕW^LD@@DNwg|q):U!CGm\rp~zk}nذa6vF ͆T!R^ę4RSG7k,KGasq>coo.nfM6Gy${Fr$'@S": C6yL@PYzXRԟ$￿+l274q#1@R" F@WD@e h8 @bK޽Np„ 7`)bL@4B @rE@SS"@#ҥ?OK!c P O?LcT yIndAy֪U:ud_|E X Pt#hB ?4k%3g[!* @Ga6m`+VA ~$*K.JP9:U+1h vj+uĉoĐ@PH1<9(~!@ ei8GۚfuQ|"t5|:k(k>@擷.I":t#G&߀) DU/x%hLOy `ʔ)t@Oቀ&!CF@kԨ >6fGU(Jʉu-ZdgϮ|c@ H|* G?fD"Ocn:+NjA4ѣ\ loT)]|𾑳!"G9M65!@+g 0o~m

- P@-6jBIΛ7A&A Mxhʖ4ʭꀤH x7wbKDhC0:!'(6(~[-H~]T |[AAm@z"`7* شiivڕ]ɒJ Q*ݎ eA PMv!J|l@r\XZӧHd;vU%!]A PP5<4C<Ȱ<) &D@tځ:r WjZv@J2@ @C#ڨQ#kܸq ^GѣG:L@0V\HPY%K1\ @0DSIj@uRjի:p45~ AWi9VBU%O89v4!{ *P 2hoǹs纶Xל:Ν;ی3X:B 9hyh{[cǎQ19vv5rPڇQH:1Ptݺuf͚tb@ D!rFMU{f͚PתU+j7a\$5XPT%z§v@H @C0)$~σɌ\ "~*%@UB: @l @c |Hƍkצ9@ DS "` BHBѤiӦVA~U&A NtP JR@W_Y^{E&O@Uuֵ/2!"E@ Ic:9S h!ڼ3PLZj&14i6O+Tzu &y@ <E-1byvۙi!ajNHAE@u@U#@+Ehܕc/^l3gD{~z'A .n*.ק >.' @9*%_mvZl@]dO?[B * #LrcR MZ*5<y~ ,Wr- POA%U/Y$!( &d)x;찃M4,X|P|PC0yH @Ǘ)ɬYlΜ9 e99B 3_m@/_h "|* ʍ52 {e ,P|PC0y ~Ŋnyo@ @[/aƼ3~<<}y7EUIhUb(&h,ܘB(!j\czw:!Yj rK[>ƍW^3Ƶl:t`zԋp;!1<'G?VW^yŵOdncbr""2 ޫ'3!|d…c|,~˼^{Yqq=Y?6@2!@*xڀ)@n @s?Ggz8jO7Nò hUsq @ȱ%kS/؆ ^۫scOgg϶7|.y'hVW_}^{5ꫯ짟~+Q(l&Uu/BF F#PflO^|E[h]pT윗U]mvv!T9/\\t>K6~2hGE=X4hP9 *$f$ F_!zVB rsYf ^ns=.X3{0r/C#[nŞ{9;ʬϗ1?Gq5e):5iĵUDtʔ)n /nV袋sGU))]f̘a8P5f.]LC?5v׻([ous%Ht؏?/r{ /IlVF kݺV׬Y]@2!@]ti>yФ?]gyrnOΝkSN5j_Zv|fA@E23|A8pi&%fꪫv%x&N}뭷nWuǎˬ=Jsw/Zj=<Ҥ/y䑮Jk-ZʣFN؎7j7ݤGm?M&~! *-sf|MTj $BL7 k_4ʈjk_:?޽^ f8l3z ۊ(A "߂_٣C:=*Kϯl7mt#)o=E$HtꫭVZi#s;C=>i+**2MOؗ_~|Y$B!Q7~zر6zh[fw38~ߦgt{ThzT8|GN cmbcOzu͛g:7w}^tMcذa)ijM=Q~=PTđ#G=z 0`AEO>ĕG׹"4ݻw/A_]#a$)a?}(};8A8GQLG(@ JSM3~mWuZ?.TRE %z^*:w\!Zu\}MOAd'tM%04WE[3ER"zA=QAU]CDwN>dwN7lذ*h F/*59O?hަ<:E{+vzm"BjUz]+0qtSW uO}W,ߺ{C=Γ xDK2:'tnVuԳF&u,+Hk@b^/ZI!]zzWj΢(}%J[FJ_^Y2(g]iӦMs͋u?T5H&CA$i=;d"׉D .zu}kO &q_5 ,V-qtWtOz*ZN@UmfO9Tow&Ei N4y# VG E?^NQO U]ՅO J.r)b馛wS"[LzJ'[h鑍p $8<- ӋeQQJꁥi= A=(tMWFĥ^r$NFU*_"ǠL.JvzKE\$B@ MoTՌ"=T>]3Q~3@/NzMj6Ȥ&,z1 QJ4ꙨkCX׉kɮᄁr$[T[rȤz3XRA-{D+Ye22G6řLU2.@<ꨣ\Zkڌx6에:!hi*t L?Io):AN s=hvAbXՑJzJQtoVRHl>mNv+(@U6EWW,Ϻa!S^=uSֹnҋ3=չ+X"FϒK%=2Lv)2DJܓ^LQ-" H3Uz)}qCęJWOsW\ڬݚ"z+S8K޸r>֧hfDI5I7> mTfJL&2OP'nw@q""ߙH^L%Od@LR A?ܵ;qU50BIRR1TzER"j[>ՓO!Ri?U=S674W{U)ASUJ:tpJQ;v )^C+ *ЧjEJ .&R+M$$Dl 95VRIAS믿5DOڅQ*CBkSO=u.q+JK,-TծFOQYeQN:u4UIUm]"S"TKjNK$@q&|A#x*OUA4/q5ԭG0h#E ՘XIW3T{SM_MNI۩,LzSMejaJ!TgITN\#d"iR .Ld< hVf` 6š hQI>U^JP\Om'cuzg,ySHTӏ:HEZL|jyLo[zӓy!CGڎ@ 27%3 PPY|ꩧܘ[*O~vʳ$1h>TdTU$@q'6bvYD@+z9XJZJj%%AD@L@ 㝐"e s$ UA%?4w92 XtT*X2& @@ )YG͠AРHrO{` XLH$/W℠8 4F`dԛSܑ h@FX@ TNHL Wc!Pa4繦>,[$A@~P$lD@鈔W^q':(#x#KX nf͚ xڵkQ{ 1:!i.sMe9jԨs!@ VgÙY +*;nf mΜ9N֨뮊v- r@#Of ʭ(hadM@~Uo֨ ^;lI'2HX@ HX&W1T/'EQzY@A@m@3ɣH h-@Vj{ΦLb-Z={Z߾}f͚Ѧ E@%@-[ 䞀o:n8ׯ~wEketc@"nkؿ3gVd  ^8lwٳgرcmܹ7ɓ@Q12E@lP3A@v WvC0r-֦Mg .?8Vs4@E@3IVTbI@ {M$DK't6nXz1!@ \2kP=s=7E@ Vw}/#G:1vZ{w8 ( N d%c+@2Gqc͛7wtA֫W/Pl VPU+!@sh ߹sg=z}ᇮ㑢;B6 UD@9@nT(@G|kO?ԵU5G}"ݻwM 8* 䜀ڀfkPh]H@SN1;r{2gZ>`g@&uR͆ >ҧ C@t̘1nn_m]XXXS/%O $P1hZFD@T1"PMbS=  Pߕq4hPn֓ ,**w}֯_Juwj'|2B@u@RʖlH1w1BꪫvMW\ <_̊G P PfC yE CZ駟oa7x U9&B@3) D]t W!YƆ f~3qmŋg\@a# G'Pa'$ƍ>sUjڿ7.m' lWk(ӧe x%@ Lm@<@Sgs=Zha 2 V9I $@]vV4+92Jt4`7W:Mu3 A@~F3GܴiU^=@|1?ӽj{Id@Q" llH"q 9(0e @ T1@ɫuc>(5؞| PaSCHN@lLGOD/0Lu0C4"&L_)~D"mJ4(ϑrGe9#C;+VXfͲfƥs6YC΁ 8_TGeٓ! XPPMӜ$Hl86atb3f̰n͛g 4jG>!@ <]sK PU#&Mr/X z!5\c?nf!@ $@U-ͤ-[,X@|G@O9}ݭڶmLxꩧZjhYA@T^Hm*x@H@4 |v7ZÆ KJڼys7СCK: )e[ hg4~| 5jHU8q[Wf @ T͙t}k} 61cw^Ȋ9 ET...vϤlc@-@[haÇ>&OlN/8s DhZTTdj7!JL޽zv衇{(; 4T&t͚56m47g ) I5b꬚9e0lRX;(yeܹssΛxjl *r H a0#KT7ps)Sȣ:\f9 @ 4"yEOUS lW$e }TgA-@ulcǎMZ^l= L£iRpa* ?~9su tR;'ڵk@03gl7V|W:,g}lΜ96i$[h^c{アBa @EPY#@ o;Znʡ޽{_nnˆM a^{ L T  n.5ѣG)Ԕ)SyeO@UD@~QSO=N8߿)*{ٓO>iwu}'"]t/1J@ l9@ ^{n3%O?gm<@o@@< ])PynQ-@.\X81APB@"\ Pࢢ"7,^CWPGvSf׿좋.*Y@:rmР xB8/ ]ӭG駟: C .kZ @ r-@3@ e:i8 -#_}Fi~;wGX @US"u `ktA֭[7?~q6x` s r P\U :Ū+U9e0%| G}^z%W}'رcs">!@ OZ <9(fGuT҂wO [^|. ͥ86R#[*{ 8 /؍7hͳQF35@ U$#'Ew/I&ـlNtj kMꫯZ-Ur Hn:a@#|ꜚ=JO<:(j, @cF @ $@=թS~E7D4nH@" uʵeT U#/ _۫5A|!P5Ъbk@@"L) @FgX @ $@e:Μ*@V;@ŋC%@-ZS @FY @ 6m䦾lԨQ1AP# 7*6 P 0Qƍ> $ @0E@eg~O~a! Pޖ[n 9R M@GkOqGa}ˊO@ KB`ڵme˖-C]$ PEj&m{Ǻ_|~wg;@ E |"0m4+..֭[-ZpȗW_}MW^g^K/%Kڟ  @S^vڪU+[~HÇ>Ȏ:(kԨ+I'd6lG}4Ka!mlx"H`ʔ)֠A"H*s=K|}~|% 'A%E@xuPL P~g=<@[z5mA 8s xa.ĤL>VV-kG"[n3ϔY@ `8  PUzo>eTO:SKֻwoW^2>*',|@F]m&ڴic'OM6%W͛gowj׮M5|X @bOou*۶mn sъ~6iĶ~ҫJ~שSz-!K,Or ;ڵs"4 ԩ3o)c7mw/ƻ͘1&NXz!4 @N`̘1wB:c?}mժUV{qMзz[' h@ @ ,ҥKjޡCӜ@wi'1-  @M/pn"UPE/OY/vUnOӧgҤI~wa;@Hl@ _ Hj\ƍG ">dȐ{1[kY/GoF%[ hUh- <#gYԢrDc=_vsϜ9 ,?`ίU BlOZ>@k}͟?T4p@={]ve?ɶb ;蠃\h$@ `8  xw]5;ɲ#Gasڥ^ZWhER6l GFz7 ĕhݜQ-A\Գ(%rרQ}s<<a!@4,@!"0uT0a/=DfdqMh:o߾|rdJ"B 4_!@yGW^ Ug "Y@UWk޽{9~ٸqL#  @bG@UIP4]vvmo@J)ac'@%O:73푂?7 /Ñ?bMkR8@U#0ydWŬfRYi]wu5' H45n@ ~akԨ瞱,_:餓^zvqǹȓ< oq @^3E?k֬YV,nР~ֿ{뭷T@\ ()ǵEv׻vqiرOF;h=L  PXXh>pV~}{ɖ[n"+~vUWڵkTB8+j@@4"UKٳ;3ѣoڴi2bk"yl @  ؿoTFR hC9ĮJ1c_ti3bu{>}X!CbWR@Zj6p2`;wv"t޼y?֯__/K@Mbt=u]ֱcG4hmذ8 袋lӦM~ vT5@ ~GW][l4o\`z]{3M"l2A_dovmu9zGٿ/t{o@GM6D@sÝB$iyAžvٱ6q)07)b .ttO|zoO<-'4nh@o_;֭~.e]fsl? _Wsϵ֭['w_7^ꫤDa!4 ^F@ VZ*8Szvyj\@?GyUw޽")YgeQŬ O/5M'ܤШmᦁIHF`ѢE%e7,϶.6zh(݂Q2G@v)s"gGGnznƍlFUgFlr~lVn>.r bhx}e2B/iO<1#iYslN?t[re X~"7vͺtb/Em*Xh\M瞳7|N8cVZv9Tԋ;(;sJ@ aI/^R  @9 {{ |iL&75PD\>!O5F@T/WN5I8qM:~'6lX:Y/ @a*vڹqɔLL@*Jl7 CO.WOv ,[_lX(1^^:itA=Β#ivz4FF?˗  lPGaj_TڵmK p{S?u:Mh<+Q/wAQN:HǏwxAd<"!@5XTZvmkm_م-q8|K_ӧ۪U,ݧ6mZI ͙3gmyS;[| lPm#W5f+MrO@^{e@0>6m-}Z^sn]z)I'qsU孹ׯ__bXz( |'܏>h}KiU-׵kWkӦ;@Y&S—<y=Xf\>":A:"_~f-H\pkīʓA6ho4VįNSO u:tfվCan"cioy}1fP\/5k̽1XQuPv/VOv^aQd-Yi@! Q|LVP (yɶ2=tLİϻ|GYe`G}d#FpA5/ʮNKwuW&N}YR=ty|zL)|dUYQ:IJ*PwBRƴj޼t~3-_o#1Rj*K:1҃[)5էa2ZW"Uռzou^۪tA)}R* e @/0Cgr)n*!T ȡĕT%q9wm@ 5L|j;T=AII4<"ɒ:9ihD풉O-/Man/5P!C᝴  @,ԯ,D.IIuRCaS'd)sPU]&1hڂVU$@ U4tML;TJj%%A J ԝ~5o @q!') @hD @ .q$ @!0 ą4. @ "qfB @œ D4"L@ иxr@ @FQ @BOR@ Ј8 3!@@\ @I@"BGa& @ h\S5jmܸ1%O?}e o/.@&Nh}-_|֭[/6lذ:~G*|Egat=㎜ۅ@Eꫯjժ^=cz8vnڵ+Y@ =\rթSǚ7onu]֦M|֭[77oծ] eXPP AolGy}׿վ;5k+O?>,X`+Vp!3gfֲiӦ]ٌ ? =ӧO7r:sLG/܎=X{g6W۪Ulܹ%AMb2RCH01cl|v;l~C/:2]s#Fpבj+4 uwɒ%a~ ~3q1ǔ=fwAlnh; Lbs=gu!=n\`ZBرp %y<]W]uZr@ S]Ê*o?OՆ聸tR?^7 64j2-Z<|'g}0:;vvNj+AOA-EIk֬i=_UMI<_K-$ :3ϴzB EH_{5wnUۏ?EE:ud7x{ ?(Ɍ/@Ft;lذL>M"21"c9Y.R7C"77|U1SHa\nͺvj{#6]s^wQ/?\ ͌$yꩧ::|p+I|Y&\.!C'|RR=zpvʼnO-jLG5ڗ):O7%:իo6ۑ֭[b^0iܸnU]]Pˎ:(DGyb$@.ҩmۚ*Zlħ5[l*:'hl"Fыnw_&RI%j+~ځ䞀ӨJKkK׷ժU[U򩇩:.SIK`…Q&TwᇗDq)·z7 /V? ]gro Rߍ'P~^/~׊)1h|"Voq-[U`*...SP|zjh%M%PlRɇ} WEEE.ҙʽFbv<_5\X @ !X @HN K!@2D!d @ 49B d4C`@6w4@h @C  P1s9lC-@a%ڠVa- vA##F;įjw@3hm!p]w̙37p3/| 67xҥ͘1~mkݺ]zn; 裏6ͦ%U~n.lCo?o5#@|`(@tM)aۻ矻SkJA-?ClРA)>ѣV]v}f}8p=+Gмq5D@믷^zmV X믿d۶m#.B٧~"jK:|Ԕgy*jjAAAb| hZ@x ѣdmڿU;`믿-Zsε ڜ9sL@ (РH hڴiT,-[իڈz%:նZ5ZkyL! #@4N:ڐ|%sڴi_"$@A6H@ 4ikaO?tW~ 7jzǻkժn@`3p@tG/l}I޽{۳>klifecyclelifecycledefunctdefunct svglite/man/figures/lifecycle-maturing.svg0000644000176200001440000000170614006506377020466 0ustar liggesuserslifecyclelifecyclematuringmaturing svglite/man/figures/logo.png0000644000176200001440000005555513670472436015647 0ustar liggesusersPNG  IHDRfiCCPsRGB IEC61966-2.1(uKCA#E,,D( h# Qb63^[VPm AQVQyKYf ` ^L6^\da{Ft0Utu< PoϬUָܿ؅U O V[J*$|"7+2C> ;8̤ }̗4ų3Ż Nj)&aQ=1H.O\Ef"$I%jA%&DHS4zbh\ uM0:8Vs0&FEsA^T6Cբ%FܚH14G =«U reLgU/ pHYs.#.#x?v IDATxwxU?I.B!(苊 lEPD"K@E6 ([Y""PfK.:{'yQ&M|M;{s\&N1v#Fj=Y;LX#;yN&CO&Cklh硫iH$NN2@G蟝IƼᾓ~dlHAlԦ7kI59 ZFyو5ufkx=pzv7M1bnsueEt&^V&^VBD.+1b0l|t]Ll6o vuVZ2O`m"ԡ]LW`QiVsxN-}m\rO{6Wvy/iS{=TrvK_?? |\_ 0.߶!z$,G_lƮt.?;THPF*:$s{G۹?`Ja9>n,Nj\T&"&)_P0O㗽x2[a#btdE!7Fy;S]k 9S>X+e#@=xr򕢆F=[_;yH:[}V 3lTeN-j?vͥ'#2;;{|͗z.Bŧh6+^1؝X:lg6FG7;廘74\ݶ mY?؉4/\dꛜ%n>g3w6Y8y?tX9;f)lEuw4c0_pC_MMhS4iZdM5g K,_>!ncnȢP"w: Hk ʮ}1e/u{\YEⶹ <,o,|o b0:..k|ܚ%ӞiOga]Kf5<&bTvj7wPõ#Wglt/ o+*ae,So6q9le1z,9{Sb#W]MWLxkYYE΢:ژ2k7o?ど~4 +I`XҲu5M={ڨ^ߚ8õ[C6jŘ@Q62L]>\W{3QfJrF!3Ʊ;(r}/.?紋YYMnj?q}'SۣKFZYq!&or~]v/;[!7f/b=&Ĩ/?wKK* Jaºhٵ0OD6gֶ\ak ~ i9 9POQK>OvB[f@at|itn|1Oga^r+?D>F ^i}#[O*tX' i-ڰbtd K`< B]'s|͇k_V(U_ILC½πŀRKo}(2&/{ageYG&{*Jj#[ۀ>"‚{TR_DŽ9"`0tv,X ?z闬R_9Ƹ5Q\'Nj\9؍e)z3*"U9{YBUQH["3z^M;zf V^+I,bHP! |A-8Y4D1O"։d!7ؙr='>H@X>RSCv,;.Rs%.H.ںFm`G~ KK4FHs0}9x i~|KqEGvj"±djs9.CtuO vd5{T9uʼnM =_Wz_,lN*8OXbbµ9|@:.]'R^>jAtR@$r3 [ڪ<)IAI* -H -GD@X_7#&eU4(G[*hܚ%]L\IHBb́S1(ub]]vnЅ׼*\+i7##~|@t376hlJ ‘~uLT \@ xYSOc?Ln򘩉`1UHR4}SQ (+hQI%Pz:Y]m׎\w73-HG`><ǁ^4-{[ת#11ݧ\LO*jY3א*jJe l^µJhco?6ƭj|n-]˵| P=,n{ _}­ZλIgG~}ݽH_ES!*^Bo}'ki8r֎svM]BJWŝ ){GU/\q{tԚT O4|id?~@YT`@똙 ǽds5+Y|?ERzknI_}Ny.=VvL *) ;aHǁ"Ơf3exO &}2vuҐYmq<|&HjD g֙6>Vz^&7@q,?pnz@ӋMk7C#4 j]&P-mczneѹG*BRo{XWK S A&@ok>S=KO$ťTbHٸ2;T\Fʱ-&ʽbtᾓ~F0t9;;Y}k?-P s1gp_=V :N ?vZcj}3R!L*(sS]ӿ {|-PDvW2D+zllŽ펝1 ˇ".BK^s' uЉ#+tOG/-ʮI1zXl﯎ꨦ;H$rZ"US"\h`7o릝~Iy&eQӣ!:)7Do"n.}lqaZ=Z-iS:zy *҉l@v|ظT'ps0s2kT"ftAF cP"}7Ġoge.,͉|,ac, ?a= 8z)[a!zyJ^d`!;r7o&RfӀEAttl5/QҀviv Hjv%T, b؉ǦT!`¨FC"7`Wg: |`zgZk(*65051O"W3&j^ҽ&ƕ]"U-y I5D`wjvuOe˃ȕm29 k|_f4{Zq϶qYDO;q,zG͞ݚ;s+oD' gXjv<51dk78Ukzs5>3^w[++ Ur䴭&.4bfތ&^;jX LP/,$Rd;Vתnb>?g,~;ukaY>MeFsOkF#OqED~>{w.N[oA֎UZVʑvw6YnÑFD.skBB%d3aom‚\\֚mJ+Lfe.W`^xUjj@:{vE(0鳭@7=ͬq&Q2neY0WXsBl[\nÑE;WʰƎ|~ͯ^($ 'ȧܹ T̞&g ηՠA ̧y}& IlDlB +Ogs!];mFlOWiyuϟ>E_mGY{`Wm>plQy6 VwG@okMZ; Ht \뫭a ^wG3ּې3 ^Y}|ؠ\ƬiR#gd=cPMƄ^~U>=f jӦ-Ǿh։rJ v,yP1WguE/ӑ{ȶNl<ɘЫn!Jmi.gƠ\]–&joɏWeDP7s7Z. =JĊ6TKA̭3 mR=]R((۔ G/_J2+˃9k 'ڙjV7:oٽP)-oJh۔ GCZ!oJmSN?{rho/ZmRc\&k#8uo6~тV/gtm;!ȧW'njP|@r FD=q(Ye3Vj# j\I#uolPUcN ȯ<^[Ka\ Pt.@8SQJRUU|S#㗽X+AP=`x|Ahq_ c|7>lIFBkwB "F@M2F re0"Uߨ\LvZĆ$"pdɱ624^EPzqme0%R˒H>Ϛ(n܄?? $H`]i}82 _ Ҳ"]MA05`$Ҋf"ҾZ0.."L ʑdMKR+YD7$Ej4Xr+fﶮ}z9Fl+2 E]ǠA8k۱kɦqtYWV;2HQw{%rUyȨ3 p!ʨbE"Jגzj#lko=Ev" 92$M0 ڻsmE^yzFG~՜7{~Hrϩڦ=2م+3{no vt7|1;+arOsch0j lNEmg_`&KHB17Ksn"uHVaVK{@&ʨ4 tq+5}[^CjPɜqb~ tN"9._.#m/Me|Gj2hD7ļ|gmvvBmiR$Bɤf#ؖW3r%x 7fJrOC{2ˊӛZ3W#WK>{Ň+b'rZ)KFqaYk6>,ƥuuǏp|:B:xꃋ6*Y=Z:5߼o1+GKBbJFG\cK6\]jh{edMcv -&z ‘Mv6N|?aK[T+rOJJM0:VzaTat{G~|>XEۡlIjɜ7Ԃ :59yrOĊ6 fXQy4*=]|tv|:Acϲ^9rv7Ϫ}ch9vHҶ-|ْ?p"\&OVzav)wS|t6Β_ެ7zXT6&.Y(̈́ L1{"VaX{&5ƻan/w ]VG.R/|vǛbTJAx*V@BX Qużg=f 39#rex]8r+W]bS6aE U͑/dǸ;}өBaԬ"_}a&ڼVyYmr)Z0:;'Poպ205((s/~qHNB7NygTjwU:A}Si9&o&ˀ䞎6RZӫζ,OL̦ ALN%Ovw|Iguyabû1kV".$2vjA ɜ_aDO!t32m'sRx$hN^ȔbXG^FuMϭz_GVZ9PYЏ6F[(Z5 XlE`H:[[*?Ieºh"o4V m=Lj2`mrak9#z[+bV#>vyW3Ube"uqːCPs:M~իM +_s焟HG 0X[ SzhXf?h8rA+HLs*ջH5yfTqڑTgZזPH.bXV@B>:LIh4"ʢvpݹgCSsS 8jW7K=ۄ{MU2 έ(4; kY7hŒڈR+~Ì-d UeZ'"p(2p2LמpsZ!j;IF{5|_w`)$j &r#rW⪓?6J^9Oe0 *h\8ٚ2}`}- DŽuQ;*Zu u H;I8)m*>#0x%zYxN<DZ;scb*;s#E U%ۑzEE鸪(2n7je.mR?XV TH3HFwU:eHHQsS? *@_JcWGrCLfl>oh Buֵ#"=Sq4Z4{H{paIf|?iXi}B|JGx}} V+"t!2` uzmزM?45D"@ɗ?3-rĖ͹ԫ;]S3`(R/MG |c`㪮 ׯ?ϠpiG2ycL:C>9y ^^},/ ;6H xS4'!8<0SU`z"9wg 5qd<#Do$@F5g:\ڤH⽷x2Ħ %3O]NH[ 1 588r,S"!ퟅM2^ҍ_վpiR>wN!fDSbP3mt1A:F:Ɣis5Sy)6qk8.<hzё∤>{rXhdI*bX=@$> Ҩ`td-Jɳ">Ɖ#iVg(*H`XMҁOY [Xvdو.!ӄq9.^!Ha+W&4ᣗ׳F5JC֧auqi-H`Q2ҽ_V)T|>G:i-5I\Er!o!jJrF!SeK#%BiTъ#BWO5Kb /Ogs' ~Uf(\l2ى$ɬ^K:B+\H\,XX`vbN#d GLP'to6qB5 {:k, ˑfjFaDĠLMPs^1 ogUUi5?5GZ |O\L"[ 9-X2ҟ>l~ً }anjN0q}4QBk#KΥ(NHXIF]*ȏ6$s%3P%I l߄B'ܘ;^X/T`mOXm$fr BM]6)6h{يU,F*2xY]:5cH5&2n0yc4[]/R ^҄">AJ  ERz|%σ\̙/C [R,@=*^h806NM1+B,ANARUsl=Z:6_׮qΚtqҐ`@WW.for<S;ħh/$3WAϮk%k=ܸ2^si;{o zĠ*>6}[G;Ab6'5ƻao3Cx t)sioUl-MbOg=q|J2[QNp3 XnCBfջdD&&ڒHn>Pwh4:$(iKLio6'ymm'ac};8A 2sS#reF>.ɬunʢkt|^I(d_oi' [Ҋeqj$仧4>5HLϷs(#oYJ9MyiU\ƻ <[3^"3E/BѦjOfuFsMmJ#w2M eX"T;|} 9ٳ֔'lI+z K ‘Au>D9ۚ՛kmX?PXT6ݧ\` |3CϤF4ЬlREoBND* =WR2M/d7iA8G/eTp*ͭ-у:(RGqkD7`6ʑA-mœ,}_eXAB{nhtމ8q54Rt-FqaM#y.fOS~՜#q<oXג#AodT&mZR%vHaJ/>?d7`]{O ֎M;:R-5֑OjLRZ!]LڄOg zss˕\Ưs`f7ŰDojK "+O,w[hau-^o҄^gW]G]ڑ,icyw]KO.bƖXV@<}q'Y2ҏ޺fT"NByROYkLg{G=T>VzR+Ìq$gVpC`oV6k=ܘ:> -`FG6tOgܚ\}>S;yѻ|:ě+<]8?yh4gPooQqs7UyAFG̈́<&f5k)E&~M`Hzz% 6r`ows¬; XH]d_-\EL\#|\f0i^fr&d Ա/?.[d$fl%)QFG$}>Nx"{A='S5dH_ayA|G5/wVcXэ* B f~b8Y۝XWd=S2ĭl46beJ\G‰8#kD|JGx}Y*RzC[ 0v;K#Y~_C*"󄋝)3{3Iwl,*b(mvθ9\ZO3(t[a1NhpKUݑ?%,:xaKai cB5Oo^h:SjF*1R !_ZO+v|ؘ5-s։Bi%V l x:O##?BbZ!o,N ,4x)ҰHʹ|80]\MwQp-O-w2`Ƭ!84:} *E!;(N R`0M%ׅĈn5_¡" Ip>MF’*x:5[ sPc{*룸~Ghc"7X[8{лNOkftX9z#ic9Rtf6'3Cyr2m;ՂĴB{C-/Kh'>D["` -6c84;P-'8Y3skl%{9%[ukn[݆[bf"jOt,Ij\…_t"5GhB}W wQ/TRz! ~B8m*0:l׈gXwijϛsW{\PYTl7P$.G8ԕgP,9ŕ5+l=Z*K#(r :H FI%W@=+>A YīO ҿ>Z(\kXTjTHFH7gk+Am-62?lOSl7}KlǓO竟JjVd29b3;URYQn̻JٗR:rfI |?I)#5f 8Ԡ^hB%mؔao%/wb[ ;J1]jcU/^w7pk#=FCX$CSh~E-8F?SO+ |!TU^U[bsX{b/T$LJxHG^6iBCE5{`p v.]'Kp&~c*fm+SMiU$ɺR#`'QNafv.#:},n~ZJ.Ko&\ή"| 9o'dTsksYkB7ԚU[Rت$p2dދ% m׉XW#-ӆ^m"EYJ=`;֭)v/zo=A;HGh#Rpf*RRs՜|+diL~^zUlUP $}®稱WEughO> Dig4FJLA<ןtqݡR;Zw`e)׳㗽*|'ǐV^N. ^ӣOQ;4j#FG:vvuP*q. 6PPTrSߪ<{V~wܫƃÒufs Qck3lހ֍TuRcsT2~ƺZEr3e:]^Bά!ޜ]Đnu03Ѷ-c׼Pud9X_ M_P&bhXR2K.qk +N.K5+WŒ{Wùŭ6~^X8nPc^/CTB1i}֯SfT-1GYH:j:[f2fq$\,Y8z;~՜PV=sf{Pۑ "H_7 MҜ*>kDY=rG1ڠQGe1[\/)koSARl=9̽@ =P9)C|qvѵY ^^vYJzFsRA є 25QOQJfm/};g<'*%>v9meՇpPR7yrr_(-c3p~ݧ\MJ*&uf*`FxmM؛{[߆O :bzn_:*m.]Jƞ)P= Q\tjgV8K[opWoc\5&tq+"V=!D{sc+(|mTb+n>~VU*=N>{,5X&Jͷ0Ӧm-GUTx _JfϝػZ~II6 7~܄'Z8hƣw@]gsutD%s8,*'Z8p[UkS,SٵO6&[܍Ni'#2;,UT%hcJ+?2#o&=T~i8T@ȉi];+o#C {­̛FBۏ%sJf&R4$=[?W (Hɠ5'UJuPHL+d@WW[ZX29e*TUЧCۨ5H"Vj1x9LFF>֯Ⱦc0gGsi'b׶[]xfv0T9J~:ʾө4oOsќξA*(RhmϻɈLz)Y}T~=R9x.տ%hcJRm8ۙUTe:5KFv%6&8ۚTj// gm.rw ٨<Vy\T⊜o܇NZ[⨐E<+Ee}Ώ F @"5HoPh\]L*3h}ˌfwB i,M1@|%3KK*/@<~Cv)\ܤFl)ZߥB6l #v-oxP}W*4y'[6 /"MbWsyȺUO? 5˰20p.ggn%u[?e\;t;3T**_|2"~]8u-"xu%8 t+TK.P頱׬#)6~]5u,bOFdvW 韻ͪ RFvBm+RX{6˸Z@Rr璊"sи^䂻ˊ?/dhtvd} ne)0Ód@֬=T(䛃s-q$džp& -GYG&I R+2b^[&`bz LdB>уW_BRԭJ]E>JTbICRg>[ga&gAx>|<;UsŁ{lM \ :3Hv*E$O/}ků[nsUl=H\sg>ĕRxԯTӺFR\ri t ]ǂD,sRGp{8IY '/T2sk=n ׄJdY v#Ek ِ~y[Lov)E+FB&p/vXiPt~ɔS} R+@+FÖ y [c9] xGJJ Ue}lX{b<|;3{*mi~5> mKQW V&? N(ΔMz\Fú;NW2M B[o>rW9ۚޗ{j}T*Jd$lLhs⺨I* u<~"i`w1o=\PJXSPqr:w Ѳe?yh_zZV#y}Y>A~6%Řw,J\.># M[q|zGk 9f8u-R>"2ܭNB%ϤѡZ#B`8si'ֶ[Argt*MJh2f͂"J]j\UǨ!D`'Aq{k,Uե=brRӻ1B[K|u+-pdoHT1}`}֭8ݒ|£'RɜSLMd{ty['6Ogl-Md+P\;*b[w3s@Hwbユr#k%^isWٱ\ x8K9+DbZAoCd@lnR="̦_GuLMd<RrG ?|NA~6%Yqs0w9ϐu:(mi~Z&C}s$*1Gb]ڳi\#ƽy fI|?(Bndx'"2N-} tŞZsbV@#3;g+t˪ `ʢ MS7b}"bLj |jU| xs 2Cۏ%o,PY}+Z?p2gŁëHMleW2J':)&^V&[(Y(T4/~}{rokՂ#?·@ kxPl;̎;UbQ`ncɗlhL2"cBUeM[BKl3 's7nX* 4dNBUꅘ*z9Xx:]Ǣ*w3 yIrK(3 q/=pӊu5aQټǮdk:Ҽ.vfv{]cjRlkJ~~Q#Q@IqЉ#?/mMibrZ`׉~Ki}+Ŝ*+~,E{cmcTs;&Ǽ~|/_g=ÝO+^z"+;ӮQIrK_ U1gGT.R]8jA½BBndq/='SYmnO~'64`i^LYߵ=O9bS20+O }K" ` 'LЩ#x 19mq(<*'ν͛ݔB\r%5pbTBIO{BYY5_Հpw4C&OhFv ᎍY6io[jȮM #YT.vf%u2g%RT+xj>6?H~Ԭl=QҼd(UәT,wǐ?CP-\̺CIqۭu91;O"7"s }k 9&6fpbZD \U`)*V%g{Ѯuen*'1$<:mL13{) LҴ҄?`+kO^Qټ3ƉNn6H?f PM@2<94lҦ-J"9]^T;O{K`G-PDIXs3YG>eE[ذTjOEd21 :<*$MӅЍb)PAYDAe@QG\GGqpCdGAgpgFAQ6 MHJ@ t M4q 6}/i,mӾ{o~sOy7d~t=dp;P%b"C_~3룂Rk2 o-OHhirүU=0}RKNOΑsӳzr Ɲrݓ"l`͎_js?tʨQ$ivak)ң$I]SRRtJN;9w7r|#T(ZA蠇 a.}~⬩}ŧeU@z1~2oZ}:7-l=TV[ E ?-Af'JCRcu+˒Ii08PagAܣ"H坐?VWtԊ z-lrOy[WLI̵^We9@ve%˲NןaUksݳe']ňJݹ}o(^$6wGߞfS-+# :XXa%œ{y%"'3cZc`FÀ=v &H-u\'՗nx'7[x<|F\!ʁ]xH*B2v/v<0v盇;h 8O(\(;#ϽrXqaS7&mJҖĕn/6TM+N t\1#VJv-^ː N .MѤGNS5c#$v1= 5__w,Q;wArUhM+KF):DڠbjMvH?`p}Rٙ'}5) z1Y$xc_L7nZ-J~w1{e*Weu"֠%&J [T|cG {@ m=]d#T@&z!-aݙO"Ϫ:w DA׳2esC|Į B@ꏝ>R |Hl\(:}k:a*qfwUn׎HtӽDVi$DE6OjQ}@@Rf#kw)lc#e]YHVL$<@sr!^KPi/;rcJ"Cfawzz&3YQZһEKR|S#$sq:p@ٹ)0 r@2y="=PD7N>y5;pBAGUM=U5v̵v 8XXN*X[CN@m.*ٹ*G >LUa@ 4ޠ 4E{Iv%q[1#:V& vwG37kP+׆fN{ޑS . Zƺ]\9TR`_V0G5);?}9_)숣vB@M2]WA-;Q7ե/6)ltةt51"2A(4;MռI*׿kwz1vQcupBFͼ{ߵGE_Np}Z i4UwVDH6F:_Ws3Xڙ;YW[2wh/mgDw65 JEG oR[@=Z dID,ÞZr)nV:@C bQ#2{aGA;zshqSy\IxD5D8+A M :O!qy(YyeGƃ'SS,X*+|Uzz<#;tPɆYdMkyfyA  u(: VsmtQmq% zһE{Y4#C֭"XOl?v`֪BF P6gm[tDh5lL;fP***aA6!FꮦZt~zmnIENDB`svglite/man/figures/lifecycle-archived.svg0000644000176200001440000000170714006506377020426 0ustar liggesusers lifecyclelifecyclearchivedarchived svglite/man/figures/logo.svg0000644000176200001440000002326213670472464015651 0ustar liggesusers svglite/man/figures/lifecycle-questioning.svg0000644000176200001440000000171414006506377021204 0ustar liggesuserslifecyclelifecyclequestioningquestioning svglite/man/figures/lifecycle-superseded.svg0000644000176200001440000000171314006506377021001 0ustar liggesusers lifecyclelifecyclesupersededsuperseded svglite/man/figures/lifecycle-stable.svg0000644000176200001440000000167414006506377020116 0ustar liggesuserslifecyclelifecyclestablestable svglite/man/figures/lifecycle-experimental.svg0000644000176200001440000000171614006506377021336 0ustar liggesuserslifecyclelifecycleexperimentalexperimental svglite/man/figures/lifecycle-deprecated.svg0000644000176200001440000000171214006506377020735 0ustar liggesuserslifecyclelifecycledeprecateddeprecated svglite/man/reexports.Rd0000644000176200001440000000104214006252155015021 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/aaa.R \docType{import} \name{reexports} \alias{reexports} \alias{register_variant} \alias{register_font} \alias{font_feature} \title{Objects exported from other packages} \keyword{internal} \description{ These objects are imported from other packages. Follow the links below to see their documentation. \describe{ \item{systemfonts}{\code{\link[systemfonts]{font_feature}}, \code{\link[systemfonts]{register_font}}, \code{\link[systemfonts]{register_variant}}} }} svglite/man/stringSVG.Rd0000644000176200001440000000076013670355302014666 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/inlineSVG.R \name{stringSVG} \alias{stringSVG} \title{Run plotting code and return svg as string} \usage{ stringSVG(code, ...) } \arguments{ \item{code}{Plotting code to execute.} \item{...}{Other arguments passed on to \code{\link{svglite}}.} } \description{ This is useful primarily for testing but can be used as an alternative to \code{\link{svgstring}()}. } \examples{ stringSVG(plot(1:10)) } \keyword{internal} svglite/man/editSVG.Rd0000644000176200001440000000114414357213724014306 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/inlineSVG.R \name{editSVG} \alias{editSVG} \title{Run plotting code and open svg in OS/system default svg viewer or editor.} \usage{ editSVG(code, ..., width = NA, height = NA) } \arguments{ \item{code}{Plotting code to execute.} \item{...}{Other arguments passed on to \code{\link{svglite}}.} \item{height, width}{Height and width in inches.} } \description{ This is useful primarily for testing or post-processing the SVG. } \examples{ if (interactive()) { editSVG(plot(1:10)) editSVG(contour(volcano)) } } \keyword{internal} svglite/man/svglite-package.Rd0000644000176200001440000000260514511446205016044 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/svglite-package.R \docType{package} \name{svglite-package} \alias{svglite-package} \alias{_PACKAGE} \title{svglite: An 'SVG' Graphics Device} \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} A graphics device for R that produces 'Scalable Vector Graphics'. 'svglite' is a fork of the older 'RSvgDevice' package. } \seealso{ Useful links: \itemize{ \item \url{https://svglite.r-lib.org} \item \url{https://github.com/r-lib/svglite} \item Report bugs at \url{https://github.com/r-lib/svglite/issues} } } \author{ \strong{Maintainer}: Thomas Lin Pedersen \email{thomas.pedersen@posit.co} (\href{https://orcid.org/0000-0002-5147-4711}{ORCID}) Authors: \itemize{ \item Hadley Wickham \email{hadley@posit.co} \item Lionel Henry \email{lionel@posit.co} \item T Jake Luciani \email{jake@apache.org} \item Matthieu Decorde \email{matthieu.decorde@ens-lyon.fr} \item Vaudor Lise \email{lise.vaudor@ens-lyon.fr} } Other contributors: \itemize{ \item Tony Plate (Early line dashing code) [contributor] \item David Gohel (Line dashing code and early raster code) [contributor] \item Yixuan Qiu (Improved styles; polypath implementation) [contributor] \item Håkon Malmedal (Opacity code) [contributor] \item Posit, PBC [copyright holder, funder] } } \keyword{internal} svglite/man/font_face.Rd0000644000176200001440000000411514006323231014710 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/fonts.R \name{font_face} \alias{font_face} \title{Create a font-face specification} \usage{ font_face( family, woff2 = NULL, woff = NULL, ttf = NULL, otf = NULL, eot = NULL, svg = NULL, local = NULL, weight = NULL, style = NULL, range = NULL, variant = NULL, stretch = NULL, feature_setting = NULL, variation_setting = NULL ) } \arguments{ \item{family}{The font family name this font should respond to.} \item{woff2, woff, ttf, otf, eot, svg}{URLs to the font in different formats. At least one must be given. Best browser support is provided by the woff format.} \item{local}{One or more font names that local installations of the font may have. If a local font is found with either of the given names it will be used and no download will happen.} \item{weight}{An optional value for the \code{font-weight} descriptor} \item{style}{An optional value for the \code{font-style} descriptor} \item{range}{An optional value for the \code{unicode-range} descriptor Will give the range of unicode values that this font will support} \item{variant}{An optional value for the \code{font-variant} descriptor} \item{stretch}{An optional value for the \code{font-stretch} descriptor} \item{feature_setting}{An optional value for the \code{font-feature-settings} descriptor It is recommended to avoid using this if possible} \item{variation_setting}{An optional value for the \code{font-variation-settings} descriptor.} } \value{ A character string with the \verb{@font-face} block. } \description{ Webfonts in SVG and HTML can either be specified manually using the \verb{@font-face} at-rule, or imported from e.g. Google Fonts using the \verb{@import} at-rule. \code{font_face()} helps you create a valid \verb{@font-face} block for the \code{web_fonts} argument in \code{\link[=svglite]{svglite()}} and \code{\link[=svgstring]{svgstring()}} functions. } \examples{ font_face( family = "MyHelvetica", ttf = "MgOpenModernaBold.ttf", local = c("Helvetica Neue Bold", "HelveticaNeue-Bold"), weight = "bold" ) } svglite/man/htmlSVG.Rd0000644000176200001440000000106314013470334014314 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/inlineSVG.R \name{htmlSVG} \alias{htmlSVG} \title{Run plotting code and view svg in RStudio Viewer or web browser.} \usage{ htmlSVG(code, ...) } \arguments{ \item{code}{Plotting code to execute.} \item{...}{Other arguments passed on to \code{\link{svglite}}.} } \description{ This is useful primarily for testing. Requires the \code{htmltools} package. } \examples{ if (interactive() && require("htmltools")) { htmlSVG(plot(1:10)) htmlSVG(hist(rnorm(100))) } } \keyword{internal} svglite/man/create_svgz.Rd0000644000176200001440000000055313712226054015312 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{create_svgz} \alias{create_svgz} \title{Convert an svg file to svgz, overwriting the old file} \usage{ create_svgz(file) } \arguments{ \item{file}{the path to the file to convert} } \description{ Convert an svg file to svgz, overwriting the old file } \keyword{internal} svglite/man/svglite.Rd0000644000176200001440000001152114511445621014451 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/SVG.R \name{svglite} \alias{svglite} \title{An SVG Graphics Driver} \usage{ svglite( filename = "Rplot\%03d.svg", width = 10, height = 8, bg = "white", pointsize = 12, standalone = TRUE, system_fonts = list(), user_fonts = list(), web_fonts = list(), id = NULL, fix_text_size = TRUE, scaling = 1, always_valid = FALSE, file ) } \arguments{ \item{filename}{The file where output will appear.} \item{height, width}{Height and width in inches.} \item{bg}{Default background color for the plot (defaults to "white").} \item{pointsize}{Default point size.} \item{standalone}{Produce a standalone svg file? If \code{FALSE}, omits xml header and default namespace.} \item{system_fonts}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} \emph{Consider using \code{\link[systemfonts:register_font]{systemfonts::register_font()}} instead}. Named list of font names to be aliased with fonts installed on your system. If unspecified, the R default families \code{sans}, \code{serif}, \code{mono} and \code{symbol} are aliased to the family returned by \code{\link[systemfonts]{font_info}()}.} \item{user_fonts}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} \emph{Consider using \code{\link[systemfonts:register_font]{systemfonts::register_font()}} instead}. Named list of fonts to be aliased with font files provided by the user rather than fonts properly installed on the system. The aliases can be fonts from the fontquiver package, strings containing a path to a font file, or a list containing \code{name} and \code{file} elements with \code{name} indicating the font alias in the SVG output and \code{file} the path to a font file.} \item{web_fonts}{A list containing web fonts to use in the SVG. The fonts will still need to be available locally on the computer running the code, but viewers of the final SVG will not need the font if specified as a web font. Web fonts can either be specified using \code{\link[=font_face]{font_face()}} or given as a single string in which case they are taken to be URL's for an \verb{@import} directive to e.g. Google Fonts.} \item{id}{A character vector of ids to assign to the generated SVG's. If creating more SVG files than supplied ids the exceeding SVG's will not have an id tag and a warning will be thrown.} \item{fix_text_size}{Should the width of strings be fixed so that it doesn't change between svg renderers depending on their font rendering? Defaults to \code{TRUE}. If \code{TRUE} each string will have the \code{textLength} CSS property set to the width calculated by systemfonts and \code{lengthAdjust='spacingAndGlyphs'}. Setting this to \code{FALSE} can be beneficial for heavy post-processing that may change content or style of strings, but may lead to inconsistencies between strings and graphic elements that depend on the dimensions of the string (e.g. label borders and background).} \item{scaling}{A scaling factor to apply to the rendered line width and text size. Useful for getting the right sizing at the dimension that you need.} \item{always_valid}{Should the svgfile be a valid svg file while it is being written to? Setting this to \code{TRUE} will incur a considerable performance hit (>50\% additional rendering time) so this should only be set to \code{TRUE} if the file is being parsed while it is still being written to.} \item{file}{Identical to \code{filename}. Provided for backward compatibility.} } \description{ This function produces graphics compliant to the current w3 svg XML standard. The driver output is currently NOT specifying a DOCTYPE DTD. } \details{ svglite provides two ways of controlling fonts: system fonts aliases and user fonts aliases. Supplying a font alias has two effects. First it determines the \code{font-family} property of all text anchors in the SVG output. Secondly, the font is used to determine the dimensions of graphical elements and has thus an influence on the overall aspect of the plots. This means that for optimal display, the font must be available on both the computer used to create the svg, and the computer used to render the svg. See the \code{fonts} vignette for more information. } \examples{ # Save to file svglite(tempfile("Rplots.svg")) plot(1:11, (-5:5)^2, type = "b", main = "Simple Example") dev.off() } \references{ \emph{W3C Scalable Vector Graphics (SVG)}: \url{https://www.w3.org/Graphics/SVG/} } \seealso{ \code{\link{pictex}}, \code{\link{postscript}}, \code{\link{Devices}} } \author{ This driver was written by T Jake Luciani \email{jakeluciani@yahoo.com} 2012: updated by Matthieu Decorde \email{matthieu.decorde@ens-lyon.fr} } \keyword{device} svglite/DESCRIPTION0000644000176200001440000000434614534641075013455 0ustar liggesusersPackage: svglite Title: An 'SVG' Graphics Device Version: 2.1.3 Authors@R: c( person("Hadley", "Wickham", , "hadley@posit.co", role = "aut"), person("Lionel", "Henry", , "lionel@posit.co", role = "aut"), person("Thomas Lin", "Pedersen", , "thomas.pedersen@posit.co", role = c("cre", "aut"), comment = c(ORCID = "0000-0002-5147-4711")), person("T Jake", "Luciani", , "jake@apache.org", role = "aut"), person("Matthieu", "Decorde", , "matthieu.decorde@ens-lyon.fr", role = "aut"), person("Vaudor", "Lise", , "lise.vaudor@ens-lyon.fr", role = "aut"), person("Tony", "Plate", role = "ctb", comment = "Early line dashing code"), person("David", "Gohel", role = "ctb", comment = "Line dashing code and early raster code"), person("Yixuan", "Qiu", role = "ctb", comment = "Improved styles; polypath implementation"), person("Håkon", "Malmedal", role = "ctb", comment = "Opacity code"), person("Posit, PBC", role = c("cph", "fnd")) ) Description: A graphics device for R that produces 'Scalable Vector Graphics'. 'svglite' is a fork of the older 'RSvgDevice' package. License: GPL (>= 2) URL: https://svglite.r-lib.org, https://github.com/r-lib/svglite BugReports: https://github.com/r-lib/svglite/issues Depends: R (>= 3.5.0) Imports: systemfonts (>= 1.0.0) Suggests: covr, fontquiver (>= 0.2.0), htmltools, knitr, rmarkdown, testthat (>= 3.0.0), xml2 (>= 1.0.0) LinkingTo: cpp11, systemfonts VignetteBuilder: knitr Config/Needs/website: tidyverse/tidytemplate Encoding: UTF-8 RoxygenNote: 7.2.3 SystemRequirements: libpng Config/testthat/edition: 3 NeedsCompilation: yes Packaged: 2023-12-08 14:53:20 UTC; thomas Author: Hadley Wickham [aut], Lionel Henry [aut], Thomas Lin Pedersen [cre, aut] (), T Jake Luciani [aut], Matthieu Decorde [aut], Vaudor Lise [aut], Tony Plate [ctb] (Early line dashing code), David Gohel [ctb] (Line dashing code and early raster code), Yixuan Qiu [ctb] (Improved styles; polypath implementation), Håkon Malmedal [ctb] (Opacity code), Posit, PBC [cph, fnd] Maintainer: Thomas Lin Pedersen Repository: CRAN Date/Publication: 2023-12-08 16:20:13 UTC svglite/build/0000755000176200001440000000000014534626740013041 5ustar liggesuserssvglite/build/vignette.rds0000644000176200001440000000034014534626740015375 0ustar liggesusersuK 0㣵BM]t4b,'׎1*473BHG#݀u'lvV"`Sh9IhA2O$Ϝ _ië)rIj`iKx<ўO͑iRam%4#5p\0X!K_wҵe r]mGgxa׎sHA' P_ɴ-svglite/tests/0000755000176200001440000000000014534575643013111 5ustar liggesuserssvglite/tests/testthat/0000755000176200001440000000000014534641075014742 5ustar liggesuserssvglite/tests/testthat/test-ids.R0000644000176200001440000000121413670365332016616 0ustar liggesuserstest_that("ids are assigned as expecter", { sd <- svgstring() plot(1:10, 1:10) plot(1:10, 1:10) dev.off() svg <- sd() expect_identical(svg[1], svg[2]) sd <- svgstring(id = "test") plot(1:10, 1:10) plot(1:10, 1:10) dev.off() svg <- sd() expect_identical(svg[1], svg[2]) expect_true(grepl("id='test'", svg[1])) sd <- svgstring(id = c("test", "test2")) plot(1:10, 1:10) plot(1:10, 1:10) expect_warning(plot(1:10, 1:10), regexp = "No id supplied for page no") dev.off() svg <- sd() expect_true(grepl("id='test'", svg[1])) expect_true(grepl("id='test2'", svg[2])) expect_false(grepl("id='test", svg[3])) }) svglite/tests/testthat/test-text.svg0000644000176200001440000000247114164571350017425 0ustar liggesusers This is a string svglite/tests/testthat/test-no-clip.svg0000644000176200001440000000270114164571350017776 0ustar liggesusers Clipping svglite/tests/testthat/test-points.R0000644000176200001440000000253614401575017017357 0ustar liggesuserslibrary(xml2) test_that("radius is not given in points", { x <- xmlSVG({ plot.new() points(0.5, 0.5, cex = 20) text(0.5, 0.5, cex = 20) }) circle <- xml_find_all(x, ".//circle") expect_equal(xml_attr(circle, "r"), "54.00") }) test_that("points are given stroke and fill", { x <- xmlSVG({ plot.new() points(0.5, 0.5, pch = 21, col = "red", bg = "blue", cex = 20) }) circle <- xml_find_all(x, ".//circle") expect_equal(style_attr(circle, "stroke"), rgb(1, 0, 0)) expect_equal(style_attr(circle, "fill"), rgb(0, 0, 1)) }) test_that("points get alpha stroke and fill given stroke and fill", { x <- xmlSVG({ plot.new() points(0.5, 0.5, pch = 21, col = rgb(1, 0, 0, 0.1), bg = rgb(0, 0, 1, 0.1), cex = 20) }) circle <- xml_find_all(x, ".//circle") expect_equal(style_attr(circle, "stroke"), rgb(1, 0, 0)) expect_equal(style_attr(circle, "stroke-opacity"), "0.10") expect_equal(style_attr(circle, "fill"), rgb(0, 0, 1)) expect_equal(style_attr(circle, "fill-opacity"), "0.10") }) test_that("points are given stroke and fill", { x <- xmlSVG({ plot.new() points(0.5, 0.5, pch = 21, col = "red", bg = NA, cex = 20) }) style <- xml_text(xml_find_first(x, "//style")) expect_match(style, "fill: none;") circle <- xml_find_all(x, ".//circle") expect_equal(style_attr(circle, "fill"), NA_character_) }) svglite/tests/testthat/test-clip.R0000644000176200001440000000123114401574700016757 0ustar liggesuserstest_that("regression test for no clipping", { svglite("test-no-clip.svg", 4, 4, user_fonts = bitstream) on.exit(dev.off()) mini_plot(c(-1, 1), c(-1, 1), asp = 1, type = "n") rect(-0.5, -0.5, 0.5, 0.5, col = "blue") text(0, 0.5, "Clipping", cex = 2, srt = 30) abline(h = 0.5, col = "red") }) test_that("regression test for clipping", { svglite("test-clip.svg", 4, 4, user_fonts = bitstream) on.exit(dev.off()) mini_plot(c(-1, 1), c(-1, 1), asp = 1, type = "n") clip(-1, 0, -1, 0) rect(-0.5, -0.5, 0.5, 0.5, col = "blue") clip(0, 1, 0, 1) text(0, 0.5, "Clipping", cex = 2, srt = 30) clip(-1, 0, 0, 1) abline(h = 0.5, col = "red") }) svglite/tests/testthat/test-lines.R0000644000176200001440000000745714401574770017171 0ustar liggesuserslibrary(xml2) test_that("segments don't have fill", { x <- xmlSVG({ plot.new() segments(0.5, 0.5, 1, 1) }) style <- xml_text(xml_find_first(x, "//style")) expect_match(style, "fill: none;") expect_equal(style_attr(xml_find_first(x, ".//line"), "fill"), NA_character_) }) test_that("lines don't have fill", { x <- xmlSVG({ plot.new() lines(c(0.5, 1, 0.5), c(0.5, 1, 1)) }) expect_equal(style_attr(xml_find_first(x, ".//polyline"), "fill"), NA_character_) }) test_that("polygons do have fill", { x <- xmlSVG({ plot.new() polygon(c(0.5, 1, 0.5), c(0.5, 1, 1), col = "red", border = "blue") }) polygon <- xml_find_first(x, ".//polygon") expect_equal(style_attr(polygon, "fill"), rgb(1, 0, 0)) expect_equal(style_attr(polygon, "stroke"), rgb(0, 0, 1)) }) test_that("polygons without border", { x <- xmlSVG({ plot.new() polygon(c(0.5, 1, 0.5), c(0.5, 1, 1), col = "red", border = NA) }) polygon <- xml_find_first(x, ".//polygon") expect_equal(style_attr(polygon, "fill"), rgb(1, 0, 0)) expect_equal(style_attr(polygon, "stroke"), "none") }) test_that("blank lines are omitted", { x <- xmlSVG(mini_plot(1:3, lty = "blank", type = "l")) expect_equal(length(xml_find_all(x, "//polygon")), 0) }) test_that("lines lty becomes stroke-dasharray", { expect_equal(dash_array(lty = 1), NA_integer_) expect_equal(dash_array(lty = 2), c(4, 4)) expect_equal(dash_array(lty = 3), c(1, 3)) expect_equal(dash_array(lty = 4), c(1, 3, 4, 3)) expect_equal(dash_array(lty = 5), c(7, 3)) expect_equal(dash_array(lty = 6), c(2, 2, 6, 2)) expect_equal(dash_array(lty = "1F"), c(1, 15)) expect_equal(dash_array(lty = "1234"), c(1, 2, 3, 4)) }) test_that("stroke-dasharray scales with lwd > 1", { expect_equal(dash_array(lty = 2, lwd = 1), c(4, 4)) expect_equal(dash_array(lty = 2, lwd = 1/2), c(4, 4)) expect_equal(dash_array(lty = 2, lwd = 1.1), c(4.4, 4.4)) expect_equal(dash_array(lty = 2, lwd = 2), c(8, 8)) }) test_that("line end shapes", { x1 <- xmlSVG({ plot.new() lines(c(0.3, 0.7), c(0.5, 0.5), lwd = 15, lend = "round") }) x2 <- xmlSVG({ plot.new() lines(c(0.3, 0.7), c(0.5, 0.5), lwd = 15, lend = "butt") }) x3 <- xmlSVG({ plot.new() lines(c(0.3, 0.7), c(0.5, 0.5), lwd = 15, lend = "square") }) style <- xml_text(xml_find_first(x1, "//style")) expect_match(style, "stroke-linecap: round;") expect_equal(style_attr(xml_find_first(x1, ".//polyline"), "stroke-linecap"), NA_character_) expect_equal(style_attr(xml_find_first(x2, ".//polyline"), "stroke-linecap"), "butt") expect_equal(style_attr(xml_find_first(x3, ".//polyline"), "stroke-linecap"), "square") }) test_that("line join shapes", { x1 <- xmlSVG({ plot.new() lines(c(0.3, 0.5, 0.7), c(0.1, 0.9, 0.1), lwd = 15, ljoin = "round") }) x2 <- xmlSVG({ plot.new() lines(c(0.3, 0.5, 0.7), c(0.1, 0.9, 0.1), lwd = 15, ljoin = "mitre", lmitre = 10) }) x3 <- xmlSVG({ plot.new() lines(c(0.3, 0.5, 0.7), c(0.1, 0.9, 0.1), lwd = 15, ljoin = "mitre", lmitre = 4) }) x4 <- xmlSVG({ plot.new() lines(c(0.3, 0.5, 0.7), c(0.1, 0.9, 0.1), lwd = 15, ljoin = "bevel") }) style <- xml_text(xml_find_first(x1, "//style")) expect_match(style, "stroke-linejoin: round;") expect_match(style, "stroke-miterlimit: 10.00;") expect_equal(style_attr(xml_find_all(x1, ".//polyline"), "stroke-linejoin"), NA_character_) expect_equal(style_attr(xml_find_all(x2, ".//polyline"), "stroke-linejoin"), "miter") expect_equal(style_attr(xml_find_all(x2, ".//polyline"), "stroke-miterlimit"), NA_character_) expect_equal(style_attr(xml_find_all(x3, ".//polyline"), "stroke-linejoin"), "miter") expect_equal(style_attr(xml_find_all(x3, ".//polyline"), "stroke-miterlimit"), "4.00") expect_equal(style_attr(xml_find_all(x4, ".//polyline"), "stroke-linejoin"), "bevel") }) svglite/tests/testthat/test-text-fonts.R0000644000176200001440000000525414401575051020154 0ustar liggesuserstest_that("font sets weight/style", { x <- xmlSVG({ plot.new() text(0.5, seq(0.9, 0.1, length = 4), "a", font = 1:4) }) text <- xml_find_all(x, ".//text") skip_on_os("windows") # win-builder has issues with systemfonts ATM expect_equal(style_attr(text, "font-weight"), c(NA, "bold", NA, "bold")) expect_equal(style_attr(text, "font-style"), c(NA, NA, "italic", "italic")) }) test_that("metrics are computed for different weight/style", { x <- xmlSVG(user_fonts = fontquiver::font_families("Bitstream Vera"), { plot.new() text(1, 1, "text") text(1, 1, "text", font = 2) text(1, 1, "text", font = 4) }) text <- xml_find_all(x, ".//text") x <- xml_attr(text, "textLength") expect_false(any(x[2:3] == x[1])) }) test_that("symbol font family is 'Symbol'", { symbol_font <- alias_lookup()["symbol"] matched_symbol_font <- paste0('"', match_family(symbol_font), '"') x <- xmlSVG({ plot(c(0, 2), c(0, 2), type = "n", axes = FALSE, xlab = "", ylab = "") text(1, 1, expression(symbol("\042"))) }) text <- xml_find_all(x, ".//text") expect_equal(style_attr(text, "font-family"), matched_symbol_font) }) test_that("throw on malformed alias", { expect_error(validate_aliases(list(mono = letters), list()), "must be scalar") expect_warning(validate_aliases(list(sans = "foobar"), list()), "not found") }) test_that("fonts are aliased", { matched <- match_family("cursive") x <- xmlSVG( system_fonts = list(sans = matched), user_fonts = list(mono = fontquiver::font_faces("Bitstream Vera", "Mono")), { plot.new() text(0.5, 0.1, "a", family = "serif") text(0.5, 0.5, "a", family = "sans") text(0.5, 0.9, "a", family = "mono") } ) text <- xml_find_all(x, ".//text") families <- style_attr(text, "font-family") expect_false(families[[1]] == '"serif"') expect_true(all(families[2:3] == paste0('"', c(matched, "Bitstream Vera Sans Mono"), '"'))) }) test_that("metrics are computed for different fonts", { aliases <- fontquiver::font_families("Bitstream Vera") x <- xmlSVG(user_fonts = aliases, { plot.new() text(0.5, 0.9, "a", family = "serif") text(0.5, 0.9, "a", family = "mono") }) text <- xml_find_all(x, ".//text") x_attr <- xml_attr(text, "textLength") y_attr <- xml_attr(text, "y") expect_false(x_attr[[1]] == x_attr[[2]]) expect_false(y_attr[[1]] == y_attr[[2]]) }) test_that("unicode characters in plotmath are handled", { rho <- as.name("\u03c1") expr <- call("*", rho, rho) x <- xmlSVG({ plot.new() text(0.5, 0.5, as.expression(expr)) }) text <- xml_find_all(x, ".//text") x_attr <- as.double(xml_attr(text, "x")) expect_true(x_attr[2] - x_attr[1] > 0) }) svglite/tests/testthat/test-path.R0000644000176200001440000000345114401575011016766 0ustar liggesuserslibrary(xml2) test_that("paths with winding fill mode", { x <- xmlSVG({ plot.new() polypath(c(.1, .1, .9, .9, NA, .2, .2, .8, .8), c(.1, .9, .9, .1, NA, .2, .8, .8, .2), col = rgb(0.5, 0.5, 0.5, 0.3), border = rgb(1, 0, 0, 0.3), rule = "winding" ) }) path <- xml_find_first(x, ".//path") expect_equal(style_attr(path, "fill-rule"), "nonzero") expect_equal(style_attr(path, "fill"), rgb(0.5, 0.5, 0.5)) expect_equal(style_attr(path, "fill-opacity"), "0.30") expect_equal(style_attr(path, "stroke"), rgb(1, 0, 0)) expect_equal(style_attr(path, "stroke-opacity"), "0.30") }) test_that("paths with evenodd fill mode", { x <- xmlSVG({ plot.new() polypath(c(.1, .1, .9, .9, NA, .2, .2, .8, .8), c(.1, .9, .9, .1, NA, .2, .8, .8, .2), col = rgb(0.5, 0.5, 0.5, 0.3), border = rgb(1, 0, 0, 0.3), rule = "evenodd" ) }) path <- xml_find_first(x, ".//path") expect_equal(style_attr(path, "fill-rule"), "evenodd") expect_equal(style_attr(path, "fill"), rgb(0.5, 0.5, 0.5)) expect_equal(style_attr(path, "fill-opacity"), "0.30") expect_equal(style_attr(path, "stroke"), rgb(1, 0, 0)) expect_equal(style_attr(path, "stroke-opacity"), "0.30") }) test_that("paths with no filling color", { x <- xmlSVG({ plot.new() polypath(c(.1, .1, .9, .9, NA, .2, .2, .8, .8), c(.1, .9, .9, .1, NA, .2, .8, .8, .2), col = NA, border = rgb(1, 0, 0, 0.3), rule = "winding" ) }) style <- xml_text(xml_find_first(x, "//style")) expect_match(style, "fill: none;") path <- xml_find_first(x, ".//path") expect_equal(style_attr(path, "fill-rule"), "nonzero") expect_equal(style_attr(path, "fill"), NA_character_) expect_equal(style_attr(path, "stroke"), rgb(1, 0, 0)) expect_equal(style_attr(path, "stroke-opacity"), "0.30") }) svglite/tests/testthat/test-colour.R0000644000176200001440000000115314401574753017346 0ustar liggesuserslibrary(xml2) test_that("transparent blacks are written", { x <- xmlSVG({ plot.new() points(0.5, 0.5, col = rgb(0, 0, 0, 0.25)) points(0.5, 0.5, col = rgb(0, 0, 0, 0.50)) points(0.5, 0.5, col = rgb(0, 0, 0, 0.75)) }) circle <- xml_find_all(x, ".//circle") expect_equal(style_attr(circle, "stroke"), rep("#000000", 3)) expect_equal(style_attr(circle, "stroke-opacity"), c("0.25", "0.50", "0.75")) }) test_that("transparent colours are not written", { x <- xmlSVG({ plot.new() points(0.5, 0.5, col = NA) }) circle <- xml_find_all(x, ".//circle") expect_length(circle, 0) }) svglite/tests/testthat/test-clip.svg0000644000176200001440000000355514164571350017374 0ustar liggesusers Clipping svglite/tests/testthat/helper-manual.R0000644000176200001440000000002513617022513017603 0ustar liggesusers init_manual_tests() svglite/tests/testthat/test-devSVG.R0000644000176200001440000000524114401574761017202 0ustar liggesuserslibrary(xml2) style_attr <- function(nodes, attr) { style <- xml_attr(nodes, "style") ifelse(grepl(sprintf("%s: [^;]*;", attr), style), gsub(sprintf(".*%s: ([^;]*);.*", attr), "\\1", style), NA_character_ ) } test_that("adds default background", { x <- xmlSVG(plot.new()) expect_equal(style_attr(xml_find_first(x, ".//rect"), "fill"), "#FFFFFF") }) test_that("adds background set by device driver", { x <- xmlSVG(plot.new(), bg = "red") expect_equal(style_attr(xml_find_first(x, ".//rect"), "fill"), rgb(1, 0, 0)) }) test_that("default background respects par", { x <- xmlSVG({ par(bg = "red") plot.new() }) expect_equal(style_attr(xml_find_first(x, ".//rect"), "fill"), rgb(1, 0, 0)) }) test_that("if bg is transparent in par(), use device driver background", { x <- xmlSVG({ par(bg = NA) plot.new() }, bg = "blue") style <- xml_text(xml_find_first(x, "//style")) expect_match(style, "fill: none;") expect_equal(style_attr(xml_find_first(x, ".//rect"), "fill"), rgb(0, 0, 1)) }) test_that("creating multiple pages is identical to creating multiple individual svgs", { set.seed(42) df <- data.frame(x = rnorm(20), y = rnorm(20)) plot_one <- function() plot(df$x, df$y) plot_two <- function() plot(df$x, df$y + 10) # strings s_multiple <- svgstring() plot_one() plot_two() dev.off() s_1 <- svgstring() plot_one() dev.off() s_2 <- svgstring() plot_two() dev.off() # index also in s_x to drop the class attribute expect_length(s_multiple(), 2L) expect_identical(s_multiple()[1L], s_1()[1L], label = "svgstring first plot") expect_identical(s_multiple()[2L], s_2()[1L], label = "svgstring second plot") # same with devices dir <- tempdir() f_multiple <- file.path(dir, "test-multiple-%03d.svg") f_single_1 <- file.path(dir, "test-single-1.svg") f_single_2 <- file.path(dir, "test-single-2.svg") f_multiple_1 <- sprintf(f_multiple, 1L) f_multiple_2 <- sprintf(f_multiple, 2L) on.exit(file.remove(f_multiple_1, f_multiple_2, f_single_1, f_single_2)) svglite(f_multiple) plot_one() plot_two() dev.off() svglite(f_single_1) plot_one() dev.off() svglite(f_single_2) plot_two() dev.off() expect_identical(readLines(f_multiple_1), readLines(f_single_1), label = "svglite first plot") expect_identical(readLines(f_multiple_2), readLines(f_single_2), label = "svglite second plot") }) test_that("ensure text leading white space will be rendered", { x <- xmlSVG(plot.new()) expect_true( grepl( "white-space: pre", xml_text(xml_find_first(x, ".//defs/style[@type = 'text/css']")) ) ) expect_equal(style_attr(xml_find_first(x, ".//rect"), "fill"), "#FFFFFF") }) svglite/tests/testthat/test-scale.R0000644000176200001440000000241214401575040017117 0ustar liggesuserslibrary("grid") # Tests requiring manual oversight are registered as such. They # specify files that should be opened with multiple browsers to make # sure the SVG appearance is consistent. Use open_manual_tests() after # running testthat to open them in your default browser. test_that("text has correct dimensions", { register_manual_test("test-scale-text.html") ttf <- fontquiver::font("Liberation", "Sans", "Regular")$ttf w <- systemfonts::string_width("foobar", path = ttf, index = 0L, res = 1e4) * 72 / 1e4 h <- max(vapply(systemfonts::glyph_info("foobar", path = ttf, index = 0L, res = 1e4)$bbox, `[[`, numeric(1), "ymax")) * 72 / 1e4 svglite("test-scale-text.svg", width = w / 72, height = h / 72, user_fonts = fontquiver::font_families("Liberation") ) on.exit(dev.off()) grid.newpage() grid.rect(0, 1, width = unit(w, "bigpts"), height = unit(h, "bigpts"), hjust = 0, vjust = 1, gp = gpar(col = "red", lwd = 1) ) grid.text("foobar", 0, 1, hjust = 0, vjust = 1, gp = gpar(fontsize = 12)) pushViewport(viewport()) }) test_that("lwd has correct dimensions", { x <- xmlSVG({ plot.new() segments(0, 1, 0, 0, lwd = 96 / 72) }) line <- xml_find_all(x, "//line") expect_equal(xml_attr(line, "style"), "stroke-width: 1.00;") }) svglite/tests/testthat/test-scale-text.html0000644000176200001440000000045213617022513020646 0ustar liggesusers This test requires the Liberation Sans family to be installed on the system.

foobar

svglite/tests/testthat/test-text.R0000644000176200001440000000453014401575117017024 0ustar liggesuserslibrary(xml2) test_that("par(cex) affects strwidth", { xmlSVG({ plot.new() w1 <- strwidth("X") par(cex = 4) w4 <- strwidth("X") }) expect_equal(w4 / w1, 4, tolerance = 1e-3) }) test_that("cex affects strwidth", { inlineSVG(height = 7, width = 7, { plot.new() w1 <- strwidth("X") w4 <- strwidth("X", cex = 4) }) expect_equal(w4 / w1, 4, tolerance = 1e-3) }) test_that("special characters are escaped", { x <- xmlSVG({ plot.new() text(0.5, 0.5, "<&>") }) # xml_text unescapes for us - this still tests that the # file parses, which it wouldn't otherwise expect_equal(xml_text(xml_find_first(x, ".//text")), "<&>") }) test_that("utf-8 characters are preserved", { skip_on_os("windows") # skip because of xml2 buglet skip_if_not(l10n_info()$`UTF-8`) x <- xmlSVG({ plot.new() text(0.5, 0.5, "\u00b5") }) # xml_text unescapes for us - this still tests that the # file parses, which it wouldn't otherwise expect_equal(xml_text(xml_find_first(x, ".//text")), "\u00b5") }) test_that("special characters are escaped", { x <- xmlSVG({ plot.new() text(0.5, 0.5, "a", col = "#113399") }) # xml_text unescapes for us - this still tests that the # file parses, which it wouldn't otherwise expect_equal(style_attr(xml_find_first(x, ".//text"), "fill"), "#113399") }) test_that("default point size is 12", { x <- xmlSVG({ plot.new() text(0.5, 0.5, "a") }) expect_equal(style_attr(xml_find_first(x, ".//text"), "font-size"), "12.00px") }) test_that("cex generates fractional font sizes", { x <- xmlSVG({ plot.new() text(0.5, 0.5, "a", cex = 0.1) }) expect_equal(style_attr(xml_find_first(x, ".//text"), "font-size"), "1.20px") }) test_that("a symbol has width greater than 0", { xmlSVG({ plot.new() strw <- strwidth(expression(symbol("\042"))) }) expect_lt(.Machine$double.eps, strw) }) test_that("strwidth and height correctly computed", { svglite("test-text.svg", 4, 4, user_fonts = bitstream) on.exit(dev.off()) plot.new() str <- "This is a string" text(0.5, 0.5, str) h <- strheight(str) w <- strwidth(str) rect(0.5 - w / 2, 0.5 - h / 2, 0.5 + w / 2, 0.5 + h / 2) }) test_that("strwidth has fallback for unknown glyphs", { xmlSVG(user_fonts = bitstream, { plot.new() w <- strwidth("正規分布") }) expect_true(w > 0) }) svglite/tests/testthat/test-scale-text.svg0000644000176200001440000000210514164571350020504 0ustar liggesusers foobar svglite/tests/testthat/helper-style.R0000644000176200001440000000064313671626142017504 0ustar liggesusersstyle_attr <- function(nodes, attr) { style <- xml2::xml_attr(nodes, "style") ifelse( grepl(sprintf("%s: [^;]*;", attr), style), gsub(sprintf(".*%s: ([^;]*);.*", attr), "\\1", style), NA_character_ ) } dash_array <- function(...) { x <- xmlSVG(mini_plot(1:3, ..., type = "l")) dash <- style_attr(xml2::xml_find_first(x, "//polyline"), "stroke-dasharray") as.numeric(strsplit(dash, ",")[[1]]) } svglite/tests/testthat/test-output.R0000644000176200001440000000127614401574777017417 0ustar liggesuserstest_that("different string and file output produce identical svg", { ## 1. Write to a file f1 <- tempfile() svglite(f1) plot(1:5) dev.off() out1 <- readLines(f1) ## 2. Write to a string stream s <- svgstring() plot(1:5) dev.off() out2 <- strsplit(s(), "\n")[[1]] expect_equal(out1, out2) }) test_that("intermediate outputs are always valid svg if always_valid=TRUE", { path <- tempfile() svglite(path, always_valid = TRUE) expect_valid_svg <- function() { expect_error(xml2::read_xml(path), NA) } mini_plot(1:10) expect_valid_svg() rect(2, 2, 3, 3) expect_valid_svg() segments(5, 5, 6, 6) expect_valid_svg() dev.off() expect_valid_svg() }) svglite/tests/testthat/test-rect.R0000644000176200001440000000122014401575031016761 0ustar liggesuserslibrary(xml2) test_that("rects equivalent regardless of direction", { x1 <- xmlSVG({ plot.new() rect(0.2, 0.2, 0.8, 0.8) }) x2 <- xmlSVG({ plot.new() rect(0.8, 0.8, 0.2, 0.2) }) rect1 <- xml_attrs(xml_find_all(x1, "./g/rect")[[1]]) rect2 <- xml_attrs(xml_find_all(x2, "./g/rect")[[1]]) expect_equal(rect1, rect2) }) test_that("fill and stroke colors", { x <- xmlSVG({ plot.new() rect(0.2, 0.2, 0.8, 0.8, col = "blue", border = "red") }) rectangle <- xml_find_all(x, "./g/rect")[[1]] expect_equal(style_attr(rectangle, "fill"), rgb(0, 0, 1)) expect_equal(style_attr(rectangle, "stroke"), rgb(1, 0, 0)) }) svglite/tests/testthat/helper-aliases.R0000644000176200001440000000007213617022513017751 0ustar liggesusers bitstream <- fontquiver::font_families("Bitstream Vera") svglite/tests/testthat/test-raster.R0000644000176200001440000000042314401575025017333 0ustar liggesuserslibrary(xml2) test_that("raster exists", { x <- xmlSVG({ image(matrix(runif(64), nrow = 8), useRaster = TRUE) }, standalone = TRUE) ns <- xml_ns(x) img <- xml_attr(xml_find_all(x, ".//d1:image", ns = ns), "xlink:href", ns = ns) expect_gt(nchar(img), 1000) }) svglite/tests/testthat.R0000644000176200001440000000056614401574564015075 0ustar liggesusers# This file is part of the standard setup for testthat. # It is recommended that you do not modify it. # # Where should you do additional test configuration? # Learn more about the roles of various files in: # * https://r-pkgs.org/tests.html # * https://testthat.r-lib.org/reference/test_package.html#special-files library(testthat) library(svglite) test_check("svglite") svglite/src/0000755000176200001440000000000014534626740012531 5ustar liggesuserssvglite/src/svglite_types.h0000644000176200001440000000002313712223356015567 0ustar liggesusers#include svglite/src/utils.h0000644000176200001440000000102514274416553014040 0ustar liggesusers#ifndef __SVGLITE_UTILS__ #define __SVGLITE_UTILS__ #include #include #include inline static double dbl_format(double x) { if (std::abs(x) < std::numeric_limits::epsilon()) return 0.00; else return x; } inline bool iequals(const std::string& a, const std::string& b) { unsigned int sz = a.size(); if (b.size() != sz) { return false; } for (unsigned int i = 0; i < sz; ++i) { if (tolower(a[i]) != tolower(b[i])) { return false; } } return true; } #endif svglite/src/SvgStream.h0000644000176200001440000001341114511442606014605 0ustar liggesusers#ifndef __SVG_STREAM__ #define __SVG_STREAM__ #include #include #include #include #include #include #include #include #include #include "utils.h" namespace svglite { namespace internal { template void write_double(T& stream, double data) { std::streamsize prec = stream.precision(); uint8_t newprec = std::fabs(data) >= 1 || data == 0. ? prec : std::ceil(-std::log10(std::fabs(data))) + 1; stream << std::setprecision(newprec) << data << std::setprecision(prec); } }} // namespace svglite::internal class SvgStream { std::unordered_set clip_ids; bool clipping = false; public: bool has_clip_id(std::string id) { return clip_ids.find(id) != clip_ids.end(); } void set_clipping(bool clip) { clipping = clip; } void add_clip_id(std::string id) { clip_ids.insert(id); } void clear_clip_ids() { clip_ids.clear(); } bool is_clipping() {return clipping;} virtual ~SvgStream() {}; virtual void write(int data) = 0; virtual void write(double data) = 0; virtual void write(const char* data) = 0; virtual void write(const std::string& data) = 0; virtual void write(char data) = 0; virtual bool is_file_stream() = 0; void put(char data) { write(data); } virtual void flush() = 0; virtual void finish(bool close) = 0; }; template SvgStream& operator<<(SvgStream& object, const T& data) { object.write(data); return object; } template <> SvgStream& operator<<(SvgStream& object, const double& data) { // Make sure negative zeros are converted to positive zero for // reproducibility of SVGs object.write(dbl_format(data)); return object; } class SvgStreamFile : public SvgStream { std::ofstream stream_; bool compress = false; std::string file = ""; bool always_valid = false; public: SvgStreamFile(const std::string& path, bool _always_valid = false) : always_valid(_always_valid) { std::string svgz_ext = path.size() > 5 ? path.substr(path.size() - 5) : ""; std::string gz_ext = path.size() > 3 ? path.substr(path.size() - 3) : ""; compress = iequals(svgz_ext, ".svgz") || iequals(gz_ext, ".gz"); file = R_ExpandFileName(path.c_str()); stream_.open(file.c_str()); if (stream_.fail()) cpp11::stop("cannot open stream %s", path.c_str()); stream_ << std::fixed << std::setprecision(2); } SvgStreamFile(const std::string& path, int pageno, bool _always_valid = false) : always_valid(_always_valid) { std::string svgz_ext = path.size() > 5 ? path.substr(path.size() - 5) : ""; std::string gz_ext = path.size() > 3 ? path.substr(path.size() - 3) : ""; compress = iequals(svgz_ext, ".svgz") || iequals(gz_ext, ".gz"); char buf[PATH_MAX+1]; snprintf(buf, PATH_MAX, path.c_str(), pageno); buf[PATH_MAX] = '\0'; file = R_ExpandFileName(buf); stream_.open(file.c_str()); if (stream_.fail()) cpp11::stop("cannot open stream %s", buf); stream_ << std::fixed << std::setprecision(2); } void write(int data) { stream_ << data; } void write(double data) { svglite::internal::write_double(stream_, data); } void write(const char* data) { stream_ << data; } void write(char data) { stream_ << data; } void write(const std::string& data) { stream_ << data; } bool is_file_stream() {return true; } // Adding a final newline here creates problems on Windows when // seeking back to original position. So we only write the newline // in finish() void flush() { if (!always_valid) { return; } stream_ << "\n"; #ifdef _WIN32 stream_.seekp(-12, std::ios_base::cur); #else stream_.seekp(-11, std::ios_base::cur); #endif } void finish(bool close) { const auto compressor = cpp11::package("svglite")["create_svgz"]; if (is_clipping()) { stream_ << "\n"; } stream_ << "\n"; stream_.flush(); clear_clip_ids(); if (compress) { compressor(cpp11::r_string(file)); } } ~SvgStreamFile() { stream_.close(); } }; class SvgStreamString : public SvgStream { std::stringstream stream_; cpp11::environment env_; public: SvgStreamString(cpp11::environment env): env_(env) { stream_ << std::fixed << std::setprecision(2); env_["is_closed"] = false; } void write(int data) { stream_ << data; } void write(double data) { svglite::internal::write_double(stream_, data); } void write(const char* data) { stream_ << data; } void write(char data) { stream_ << data; } void write(const std::string& data) { stream_ << data; } bool is_file_stream() {return false; } void flush() { } void finish(bool close) { // When device is closed, stream_ will be destroyed, so we can no longer // get the svg string from stream_. In this case, we save the final string // to the environment env, so that R can read from env$svg_string even // after device is closed. env_["is_closed"] = close; stream_.flush(); std::string svgstr = stream_.str(); // If the current svg is empty, we also make the string empty // Otherwise append "" to make it a valid SVG if(!svgstr.empty()) { if (is_clipping()) { svgstr.append("\n"); } svgstr.append(""); } if (env_.exists("svg_string")) { cpp11::writable::strings str(env_["svg_string"]); str.push_back(svgstr.c_str()); env_["svg_string"] = str; } else { env_["svg_string"] = svgstr; } // clear the stream stream_.str(std::string()); stream_.clear(); clear_clip_ids(); } std::stringstream* string_src() { return &stream_; } }; #endif svglite/src/Makevars0000644000176200001440000000020114511442606014206 0ustar liggesusers# Keep C++11 for now due to a bug in some Linux: https://github.com/r-lib/vdiffr/issues/137 CXX_STD = CXX11 PKG_LIBS = -lpng -lz svglite/src/Makevars.win0000644000176200001440000000042614155671704015022 0ustar liggesusersVERSION = 2.7.4 RWINLIB = ../windows/harfbuzz-${VERSION} PKG_CPPFLAGS = -I${RWINLIB}/include PKG_LIBS = -L${RWINLIB}/lib${R_ARCH}${CRT} -lpng -lz all: clean winlibs winlibs: "${R_HOME}/bin${R_ARCH_BIN}/Rscript.exe" "../tools/winlibs.R" ${VERSION} clean: rm -f $(OBJECTS) svglite/src/tinyformat.h0000644000176200001440000013640613712223356015101 0ustar liggesusers// tinyformat.h // Copyright (C) 2011, Chris Foster [chris42f (at) gmail (d0t) com] // // Boost Software License - Version 1.0 // // Permission is hereby granted, free of charge, to any person or organization // obtaining a copy of the software and accompanying documentation covered by // this license (the "Software") to use, reproduce, display, distribute, // execute, and transmit the Software, and to prepare derivative works of the // Software, and to permit third-parties to whom the Software is furnished to // do so, all subject to the following: // // The copyright notices in the Software and this entire statement, including // the above license grant, this restriction and the following disclaimer, // must be included in all copies of the Software, in whole or in part, and // all derivative works of the Software, unless such copies or derivative // works are solely in the form of machine-executable object code generated by // a source language processor. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. //------------------------------------------------------------------------------ // Tinyformat: A minimal type safe printf replacement // // tinyformat.h is a type safe printf replacement library in a single C++ // header file. Design goals include: // // * Type safety and extensibility for user defined types. // * C99 printf() compatibility, to the extent possible using std::ostream // * POSIX extension for positional arguments // * Simplicity and minimalism. A single header file to include and distribute // with your projects. // * Augment rather than replace the standard stream formatting mechanism // * C++98 support, with optional C++11 niceties // // // Main interface example usage // ---------------------------- // // To print a date to std::cout for American usage: // // std::string weekday = "Wednesday"; // const char* month = "July"; // size_t day = 27; // long hour = 14; // int min = 44; // // tfm::printf("%s, %s %d, %.2d:%.2d\n", weekday, month, day, hour, min); // // POSIX extension for positional arguments is available. // The ability to rearrange formatting arguments is an important feature // for localization because the word order may vary in different languages. // // Previous example for German usage. Arguments are reordered: // // tfm::printf("%1$s, %3$d. %2$s, %4$d:%5$.2d\n", weekday, month, day, hour, min); // // The strange types here emphasize the type safety of the interface; it is // possible to print a std::string using the "%s" conversion, and a // size_t using the "%d" conversion. A similar result could be achieved // using either of the tfm::format() functions. One prints on a user provided // stream: // // tfm::format(std::cerr, "%s, %s %d, %.2d:%.2d\n", // weekday, month, day, hour, min); // // The other returns a std::string: // // std::string date = tfm::format("%s, %s %d, %.2d:%.2d\n", // weekday, month, day, hour, min); // std::cout << date; // // These are the three primary interface functions. There is also a // convenience function printfln() which appends a newline to the usual result // of printf() for super simple logging. // // // User defined format functions // ----------------------------- // // Simulating variadic templates in C++98 is pretty painful since it requires // writing out the same function for each desired number of arguments. To make // this bearable tinyformat comes with a set of macros which are used // internally to generate the API, but which may also be used in user code. // // The three macros TINYFORMAT_ARGTYPES(n), TINYFORMAT_VARARGS(n) and // TINYFORMAT_PASSARGS(n) will generate a list of n argument types, // type/name pairs and argument names respectively when called with an integer // n between 1 and 16. We can use these to define a macro which generates the // desired user defined function with n arguments. To generate all 16 user // defined function bodies, use the macro TINYFORMAT_FOREACH_ARGNUM. For an // example, see the implementation of printf() at the end of the source file. // // Sometimes it's useful to be able to pass a list of format arguments through // to a non-template function. The FormatList class is provided as a way to do // this by storing the argument list in a type-opaque way. Continuing the // example from above, we construct a FormatList using makeFormatList(): // // FormatListRef formatList = tfm::makeFormatList(weekday, month, day, hour, min); // // The format list can now be passed into any non-template function and used // via a call to the vformat() function: // // tfm::vformat(std::cout, "%s, %s %d, %.2d:%.2d\n", formatList); // // // Additional API information // -------------------------- // // Error handling: Define TINYFORMAT_ERROR to customize the error handling for // format strings which are unsupported or have the wrong number of format // specifiers (calls assert() by default). // // User defined types: Uses operator<< for user defined types by default. // Overload formatValue() for more control. #ifndef TINYFORMAT_H_INCLUDED #define TINYFORMAT_H_INCLUDED namespace tinyformat {} //------------------------------------------------------------------------------ // Config section. Customize to your liking! // Namespace alias to encourage brevity namespace tfm = tinyformat; // Error handling; calls assert() by default. // #define TINYFORMAT_ERROR(reasonString) your_error_handler(reasonString) // Define for C++11 variadic templates which make the code shorter & more // general. If you don't define this, C++11 support is autodetected below. // #define TINYFORMAT_USE_VARIADIC_TEMPLATES //------------------------------------------------------------------------------ // Implementation details. #include #include #include #ifndef TINYFORMAT_ASSERT # include # define TINYFORMAT_ASSERT(cond) assert(cond) #endif #ifndef TINYFORMAT_ERROR # include # define TINYFORMAT_ERROR(reason) assert(0 && reason) #endif #if !defined(TINYFORMAT_USE_VARIADIC_TEMPLATES) && !defined(TINYFORMAT_NO_VARIADIC_TEMPLATES) # ifdef __GXX_EXPERIMENTAL_CXX0X__ # define TINYFORMAT_USE_VARIADIC_TEMPLATES # endif #endif #if defined(__GLIBCXX__) && __GLIBCXX__ < 20080201 // std::showpos is broken on old libstdc++ as provided with macOS. See // http://gcc.gnu.org/ml/libstdc++/2007-11/msg00075.html # define TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND #endif #ifdef __APPLE__ // Workaround macOS linker warning: Xcode uses different default symbol // visibilities for static libs vs executables (see issue #25) # define TINYFORMAT_HIDDEN __attribute__((visibility("hidden"))) #else # define TINYFORMAT_HIDDEN #endif namespace tinyformat { //------------------------------------------------------------------------------ namespace detail { // Test whether type T1 is convertible to type T2 template struct is_convertible { private: // two types of different size struct fail { char dummy[2]; }; struct succeed { char dummy; }; // Try to convert a T1 to a T2 by plugging into tryConvert static fail tryConvert(...); static succeed tryConvert(const T2&); static const T1& makeT1(); public: # ifdef _MSC_VER // Disable spurious loss of precision warnings in tryConvert(makeT1()) # pragma warning(push) # pragma warning(disable:4244) # pragma warning(disable:4267) # endif // Standard trick: the (...) version of tryConvert will be chosen from // the overload set only if the version taking a T2 doesn't match. // Then we compare the sizes of the return types to check which // function matched. Very neat, in a disgusting kind of way :) static const bool value = sizeof(tryConvert(makeT1())) == sizeof(succeed); # ifdef _MSC_VER # pragma warning(pop) # endif }; // Detect when a type is not a wchar_t string template struct is_wchar { typedef int tinyformat_wchar_is_not_supported; }; template<> struct is_wchar {}; template<> struct is_wchar {}; template struct is_wchar {}; template struct is_wchar {}; // Format the value by casting to type fmtT. This default implementation // should never be called. template::value> struct formatValueAsType { static void invoke(std::ostream& /*out*/, const T& /*value*/) { TINYFORMAT_ASSERT(0); } }; // Specialized version for types that can actually be converted to fmtT, as // indicated by the "convertible" template parameter. template struct formatValueAsType { static void invoke(std::ostream& out, const T& value) { out << static_cast(value); } }; #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND template::value> struct formatZeroIntegerWorkaround { static bool invoke(std::ostream& /**/, const T& /**/) { return false; } }; template struct formatZeroIntegerWorkaround { static bool invoke(std::ostream& out, const T& value) { if (static_cast(value) == 0 && out.flags() & std::ios::showpos) { out << "+0"; return true; } return false; } }; #endif // TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND // Convert an arbitrary type to integer. The version with convertible=false // throws an error. template::value> struct convertToInt { static int invoke(const T& /*value*/) { TINYFORMAT_ERROR("tinyformat: Cannot convert from argument type to " "integer for use as variable width or precision"); return 0; } }; // Specialization for convertToInt when conversion is possible template struct convertToInt { static int invoke(const T& value) { return static_cast(value); } }; // Format at most ntrunc characters to the given stream. template inline void formatTruncated(std::ostream& out, const T& value, int ntrunc) { std::ostringstream tmp; tmp << value; std::string result = tmp.str(); out.write(result.c_str(), (std::min)(ntrunc, static_cast(result.size()))); } #define TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(type) \ inline void formatTruncated(std::ostream& out, type* value, int ntrunc) \ { \ std::streamsize len = 0; \ while (len < ntrunc && value[len] != 0) \ ++len; \ out.write(value, len); \ } // Overload for const char* and char*. Could overload for signed & unsigned // char too, but these are technically unneeded for printf compatibility. TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(const char) TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR(char) #undef TINYFORMAT_DEFINE_FORMAT_TRUNCATED_CSTR } // namespace detail //------------------------------------------------------------------------------ // Variable formatting functions. May be overridden for user-defined types if // desired. /// Format a value into a stream, delegating to operator<< by default. /// /// Users may override this for their own types. When this function is called, /// the stream flags will have been modified according to the format string. /// The format specification is provided in the range [fmtBegin, fmtEnd). For /// truncating conversions, ntrunc is set to the desired maximum number of /// characters, for example "%.7s" calls formatValue with ntrunc = 7. /// /// By default, formatValue() uses the usual stream insertion operator /// operator<< to format the type T, with special cases for the %c and %p /// conversions. template inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, const char* fmtEnd, int ntrunc, const T& value) { #ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS // Since we don't support printing of wchar_t using "%ls", make it fail at // compile time in preference to printing as a void* at runtime. typedef typename detail::is_wchar::tinyformat_wchar_is_not_supported DummyType; (void) DummyType(); // avoid unused type warning with gcc-4.8 #endif // The mess here is to support the %c and %p conversions: if these // conversions are active we try to convert the type to a char or const // void* respectively and format that instead of the value itself. For the // %p conversion it's important to avoid dereferencing the pointer, which // could otherwise lead to a crash when printing a dangling (const char*). const bool canConvertToChar = detail::is_convertible::value; const bool canConvertToVoidPtr = detail::is_convertible::value; if (canConvertToChar && *(fmtEnd-1) == 'c') detail::formatValueAsType::invoke(out, value); else if (canConvertToVoidPtr && *(fmtEnd-1) == 'p') detail::formatValueAsType::invoke(out, value); #ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND else if (detail::formatZeroIntegerWorkaround::invoke(out, value)) /**/; #endif else if (ntrunc >= 0) { // Take care not to overread C strings in truncating conversions like // "%.4s" where at most 4 characters may be read. detail::formatTruncated(out, value, ntrunc); } else out << value; } // Overloaded version for char types to support printing as an integer #define TINYFORMAT_DEFINE_FORMATVALUE_CHAR(charType) \ inline void formatValue(std::ostream& out, const char* /*fmtBegin*/, \ const char* fmtEnd, int /**/, charType value) \ { \ switch (*(fmtEnd-1)) { \ case 'u': case 'd': case 'i': case 'o': case 'X': case 'x': \ out << static_cast(value); break; \ default: \ out << value; break; \ } \ } // per 3.9.1: char, signed char and unsigned char are all distinct types TINYFORMAT_DEFINE_FORMATVALUE_CHAR(char) TINYFORMAT_DEFINE_FORMATVALUE_CHAR(signed char) TINYFORMAT_DEFINE_FORMATVALUE_CHAR(unsigned char) #undef TINYFORMAT_DEFINE_FORMATVALUE_CHAR //------------------------------------------------------------------------------ // Tools for emulating variadic templates in C++98. The basic idea here is // stolen from the boost preprocessor metaprogramming library and cut down to // be just general enough for what we need. #define TINYFORMAT_ARGTYPES(n) TINYFORMAT_ARGTYPES_ ## n #define TINYFORMAT_VARARGS(n) TINYFORMAT_VARARGS_ ## n #define TINYFORMAT_PASSARGS(n) TINYFORMAT_PASSARGS_ ## n #define TINYFORMAT_PASSARGS_TAIL(n) TINYFORMAT_PASSARGS_TAIL_ ## n // To keep it as transparent as possible, the macros below have been generated // using python via the excellent cog code generation script. This avoids // the need for a bunch of complex (but more general) preprocessor tricks as // used in boost.preprocessor. // // To rerun the code generation in place, use `cog -r tinyformat.h` // (see http://nedbatchelder.com/code/cog). Alternatively you can just create // extra versions by hand. /*[[[cog maxParams = 16 def makeCommaSepLists(lineTemplate, elemTemplate, startInd=1): for j in range(startInd,maxParams+1): list = ', '.join([elemTemplate % {'i':i} for i in range(startInd,j+1)]) cog.outl(lineTemplate % {'j':j, 'list':list}) makeCommaSepLists('#define TINYFORMAT_ARGTYPES_%(j)d %(list)s', 'class T%(i)d') cog.outl() makeCommaSepLists('#define TINYFORMAT_VARARGS_%(j)d %(list)s', 'const T%(i)d& v%(i)d') cog.outl() makeCommaSepLists('#define TINYFORMAT_PASSARGS_%(j)d %(list)s', 'v%(i)d') cog.outl() cog.outl('#define TINYFORMAT_PASSARGS_TAIL_1') makeCommaSepLists('#define TINYFORMAT_PASSARGS_TAIL_%(j)d , %(list)s', 'v%(i)d', startInd = 2) cog.outl() cog.outl('#define TINYFORMAT_FOREACH_ARGNUM(m) \\\n ' + ' '.join(['m(%d)' % (j,) for j in range(1,maxParams+1)])) ]]]*/ #define TINYFORMAT_ARGTYPES_1 class T1 #define TINYFORMAT_ARGTYPES_2 class T1, class T2 #define TINYFORMAT_ARGTYPES_3 class T1, class T2, class T3 #define TINYFORMAT_ARGTYPES_4 class T1, class T2, class T3, class T4 #define TINYFORMAT_ARGTYPES_5 class T1, class T2, class T3, class T4, class T5 #define TINYFORMAT_ARGTYPES_6 class T1, class T2, class T3, class T4, class T5, class T6 #define TINYFORMAT_ARGTYPES_7 class T1, class T2, class T3, class T4, class T5, class T6, class T7 #define TINYFORMAT_ARGTYPES_8 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8 #define TINYFORMAT_ARGTYPES_9 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9 #define TINYFORMAT_ARGTYPES_10 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10 #define TINYFORMAT_ARGTYPES_11 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11 #define TINYFORMAT_ARGTYPES_12 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12 #define TINYFORMAT_ARGTYPES_13 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13 #define TINYFORMAT_ARGTYPES_14 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14 #define TINYFORMAT_ARGTYPES_15 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15 #define TINYFORMAT_ARGTYPES_16 class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16 #define TINYFORMAT_VARARGS_1 const T1& v1 #define TINYFORMAT_VARARGS_2 const T1& v1, const T2& v2 #define TINYFORMAT_VARARGS_3 const T1& v1, const T2& v2, const T3& v3 #define TINYFORMAT_VARARGS_4 const T1& v1, const T2& v2, const T3& v3, const T4& v4 #define TINYFORMAT_VARARGS_5 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5 #define TINYFORMAT_VARARGS_6 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6 #define TINYFORMAT_VARARGS_7 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7 #define TINYFORMAT_VARARGS_8 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8 #define TINYFORMAT_VARARGS_9 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9 #define TINYFORMAT_VARARGS_10 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10 #define TINYFORMAT_VARARGS_11 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11 #define TINYFORMAT_VARARGS_12 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12 #define TINYFORMAT_VARARGS_13 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13 #define TINYFORMAT_VARARGS_14 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14 #define TINYFORMAT_VARARGS_15 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15 #define TINYFORMAT_VARARGS_16 const T1& v1, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7, const T8& v8, const T9& v9, const T10& v10, const T11& v11, const T12& v12, const T13& v13, const T14& v14, const T15& v15, const T16& v16 #define TINYFORMAT_PASSARGS_1 v1 #define TINYFORMAT_PASSARGS_2 v1, v2 #define TINYFORMAT_PASSARGS_3 v1, v2, v3 #define TINYFORMAT_PASSARGS_4 v1, v2, v3, v4 #define TINYFORMAT_PASSARGS_5 v1, v2, v3, v4, v5 #define TINYFORMAT_PASSARGS_6 v1, v2, v3, v4, v5, v6 #define TINYFORMAT_PASSARGS_7 v1, v2, v3, v4, v5, v6, v7 #define TINYFORMAT_PASSARGS_8 v1, v2, v3, v4, v5, v6, v7, v8 #define TINYFORMAT_PASSARGS_9 v1, v2, v3, v4, v5, v6, v7, v8, v9 #define TINYFORMAT_PASSARGS_10 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 #define TINYFORMAT_PASSARGS_11 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 #define TINYFORMAT_PASSARGS_12 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 #define TINYFORMAT_PASSARGS_13 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 #define TINYFORMAT_PASSARGS_14 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 #define TINYFORMAT_PASSARGS_15 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 #define TINYFORMAT_PASSARGS_16 v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 #define TINYFORMAT_PASSARGS_TAIL_1 #define TINYFORMAT_PASSARGS_TAIL_2 , v2 #define TINYFORMAT_PASSARGS_TAIL_3 , v2, v3 #define TINYFORMAT_PASSARGS_TAIL_4 , v2, v3, v4 #define TINYFORMAT_PASSARGS_TAIL_5 , v2, v3, v4, v5 #define TINYFORMAT_PASSARGS_TAIL_6 , v2, v3, v4, v5, v6 #define TINYFORMAT_PASSARGS_TAIL_7 , v2, v3, v4, v5, v6, v7 #define TINYFORMAT_PASSARGS_TAIL_8 , v2, v3, v4, v5, v6, v7, v8 #define TINYFORMAT_PASSARGS_TAIL_9 , v2, v3, v4, v5, v6, v7, v8, v9 #define TINYFORMAT_PASSARGS_TAIL_10 , v2, v3, v4, v5, v6, v7, v8, v9, v10 #define TINYFORMAT_PASSARGS_TAIL_11 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 #define TINYFORMAT_PASSARGS_TAIL_12 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 #define TINYFORMAT_PASSARGS_TAIL_13 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 #define TINYFORMAT_PASSARGS_TAIL_14 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 #define TINYFORMAT_PASSARGS_TAIL_15 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 #define TINYFORMAT_PASSARGS_TAIL_16 , v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 #define TINYFORMAT_FOREACH_ARGNUM(m) \ m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) m(10) m(11) m(12) m(13) m(14) m(15) m(16) //[[[end]]] namespace detail { // Type-opaque holder for an argument to format(), with associated actions on // the type held as explicit function pointers. This allows FormatArg's for // each argument to be allocated as a homogeneous array inside FormatList // whereas a naive implementation based on inheritance does not. class FormatArg { public: FormatArg() : m_value(NULL), m_formatImpl(NULL), m_toIntImpl(NULL) { } template FormatArg(const T& value) // C-style cast here allows us to also remove volatile; we put it // back in the *Impl functions before dereferencing to avoid UB. : m_value((const void*)(&value)), m_formatImpl(&formatImpl), m_toIntImpl(&toIntImpl) { } void format(std::ostream& out, const char* fmtBegin, const char* fmtEnd, int ntrunc) const { TINYFORMAT_ASSERT(m_value); TINYFORMAT_ASSERT(m_formatImpl); m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value); } int toInt() const { TINYFORMAT_ASSERT(m_value); TINYFORMAT_ASSERT(m_toIntImpl); return m_toIntImpl(m_value); } private: template TINYFORMAT_HIDDEN static void formatImpl(std::ostream& out, const char* fmtBegin, const char* fmtEnd, int ntrunc, const void* value) { formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast(value)); } template TINYFORMAT_HIDDEN static int toIntImpl(const void* value) { return convertToInt::invoke(*static_cast(value)); } const void* m_value; void (*m_formatImpl)(std::ostream& out, const char* fmtBegin, const char* fmtEnd, int ntrunc, const void* value); int (*m_toIntImpl)(const void* value); }; // Parse and return an integer from the string c, as atoi() // On return, c is set to one past the end of the integer. inline int parseIntAndAdvance(const char*& c) { int i = 0; for (;*c >= '0' && *c <= '9'; ++c) i = 10*i + (*c - '0'); return i; } // Parse width or precision `n` from format string pointer `c`, and advance it // to the next character. If an indirection is requested with `*`, the argument // is read from `args[argIndex]` and `argIndex` is incremented (or read // from `args[n]` in positional mode). Returns true if one or more // characters were read. inline bool parseWidthOrPrecision(int& n, const char*& c, bool positionalMode, const detail::FormatArg* args, int& argIndex, int numArgs) { if (*c >= '0' && *c <= '9') { n = parseIntAndAdvance(c); } else if (*c == '*') { ++c; n = 0; if (positionalMode) { int pos = parseIntAndAdvance(c) - 1; if (*c != '$') TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); if (pos >= 0 && pos < numArgs) n = args[pos].toInt(); else TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); ++c; } else { if (argIndex < numArgs) n = args[argIndex++].toInt(); else TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width or precision"); } } else { return false; } return true; } // Print literal part of format string and return next format spec position. // // Skips over any occurrences of '%%', printing a literal '%' to the output. // The position of the first % character of the next nontrivial format spec is // returned, or the end of string. inline const char* printFormatStringLiteral(std::ostream& out, const char* fmt) { const char* c = fmt; for (;; ++c) { if (*c == '\0') { out.write(fmt, c - fmt); return c; } else if (*c == '%') { out.write(fmt, c - fmt); if (*(c+1) != '%') return c; // for "%%", tack trailing % onto next literal section. fmt = ++c; } } } // Parse a format string and set the stream state accordingly. // // The format mini-language recognized here is meant to be the one from C99, // with the form "%[flags][width][.precision][length]type" with POSIX // positional arguments extension. // // POSIX positional arguments extension: // Conversions can be applied to the nth argument after the format in // the argument list, rather than to the next unused argument. In this case, // the conversion specifier character % (see below) is replaced by the sequence // "%n$", where n is a decimal integer in the range [1,{NL_ARGMAX}], // giving the position of the argument in the argument list. This feature // provides for the definition of format strings that select arguments // in an order appropriate to specific languages. // // The format can contain either numbered argument conversion specifications // (that is, "%n$" and "*m$"), or unnumbered argument conversion specifications // (that is, % and * ), but not both. The only exception to this is that %% // can be mixed with the "%n$" form. The results of mixing numbered and // unnumbered argument specifications in a format string are undefined. // When numbered argument specifications are used, specifying the Nth argument // requires that all the leading arguments, from the first to the (N-1)th, // are specified in the format string. // // In format strings containing the "%n$" form of conversion specification, // numbered arguments in the argument list can be referenced from the format // string as many times as required. // // Formatting options which can't be natively represented using the ostream // state are returned in spacePadPositive (for space padded positive numbers) // and ntrunc (for truncating conversions). argIndex is incremented if // necessary to pull out variable width and precision. The function returns a // pointer to the character after the end of the current format spec. inline const char* streamStateFromFormat(std::ostream& out, bool& positionalMode, bool& spacePadPositive, int& ntrunc, const char* fmtStart, const detail::FormatArg* args, int& argIndex, int numArgs) { TINYFORMAT_ASSERT(*fmtStart == '%'); // Reset stream state to defaults. out.width(0); out.precision(6); out.fill(' '); // Reset most flags; ignore irrelevant unitbuf & skipws. out.unsetf(std::ios::adjustfield | std::ios::basefield | std::ios::floatfield | std::ios::showbase | std::ios::boolalpha | std::ios::showpoint | std::ios::showpos | std::ios::uppercase); bool precisionSet = false; bool widthSet = false; int widthExtra = 0; const char* c = fmtStart + 1; // 1) Parse an argument index (if followed by '$') or a width possibly // preceded with '0' flag. if (*c >= '0' && *c <= '9') { const char tmpc = *c; int value = parseIntAndAdvance(c); if (*c == '$') { // value is an argument index if (value > 0 && value <= numArgs) argIndex = value - 1; else TINYFORMAT_ERROR("tinyformat: Positional argument out of range"); ++c; positionalMode = true; } else if (positionalMode) { TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); } else { if (tmpc == '0') { // Use internal padding so that numeric values are // formatted correctly, eg -00010 rather than 000-10 out.fill('0'); out.setf(std::ios::internal, std::ios::adjustfield); } if (value != 0) { // Nonzero value means that we parsed width. widthSet = true; out.width(value); } } } else if (positionalMode) { TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one"); } // 2) Parse flags and width if we did not do it in previous step. if (!widthSet) { // Parse flags for (;; ++c) { switch (*c) { case '#': out.setf(std::ios::showpoint | std::ios::showbase); continue; case '0': // overridden by left alignment ('-' flag) if (!(out.flags() & std::ios::left)) { // Use internal padding so that numeric values are // formatted correctly, eg -00010 rather than 000-10 out.fill('0'); out.setf(std::ios::internal, std::ios::adjustfield); } continue; case '-': out.fill(' '); out.setf(std::ios::left, std::ios::adjustfield); continue; case ' ': // overridden by show positive sign, '+' flag. if (!(out.flags() & std::ios::showpos)) spacePadPositive = true; continue; case '+': out.setf(std::ios::showpos); spacePadPositive = false; widthExtra = 1; continue; default: break; } break; } // Parse width int width = 0; widthSet = parseWidthOrPrecision(width, c, positionalMode, args, argIndex, numArgs); if (widthSet) { if (width < 0) { // negative widths correspond to '-' flag set out.fill(' '); out.setf(std::ios::left, std::ios::adjustfield); width = -width; } out.width(width); } } // 3) Parse precision if (*c == '.') { ++c; int precision = 0; parseWidthOrPrecision(precision, c, positionalMode, args, argIndex, numArgs); // Presence of `.` indicates precision set, unless the inferred value // was negative in which case the default is used. precisionSet = precision >= 0; if (precisionSet) out.precision(precision); } // 4) Ignore any C99 length modifier while (*c == 'l' || *c == 'h' || *c == 'L' || *c == 'j' || *c == 'z' || *c == 't') { ++c; } // 5) We're up to the conversion specifier character. // Set stream flags based on conversion specifier (thanks to the // boost::format class for forging the way here). bool intConversion = false; switch (*c) { case 'u': case 'd': case 'i': out.setf(std::ios::dec, std::ios::basefield); intConversion = true; break; case 'o': out.setf(std::ios::oct, std::ios::basefield); intConversion = true; break; case 'X': out.setf(std::ios::uppercase); // Falls through case 'x': case 'p': out.setf(std::ios::hex, std::ios::basefield); intConversion = true; break; case 'E': out.setf(std::ios::uppercase); // Falls through case 'e': out.setf(std::ios::scientific, std::ios::floatfield); out.setf(std::ios::dec, std::ios::basefield); break; case 'F': out.setf(std::ios::uppercase); // Falls through case 'f': out.setf(std::ios::fixed, std::ios::floatfield); break; case 'A': out.setf(std::ios::uppercase); // Falls through case 'a': # ifdef _MSC_VER // Workaround https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html // by always setting maximum precision on MSVC to avoid precision // loss for doubles. out.precision(13); # endif out.setf(std::ios::fixed | std::ios::scientific, std::ios::floatfield); break; case 'G': out.setf(std::ios::uppercase); // Falls through case 'g': out.setf(std::ios::dec, std::ios::basefield); // As in boost::format, let stream decide float format. out.flags(out.flags() & ~std::ios::floatfield); break; case 'c': // Handled as special case inside formatValue() break; case 's': if (precisionSet) ntrunc = static_cast(out.precision()); // Make %s print Booleans as "true" and "false" out.setf(std::ios::boolalpha); break; case 'n': // Not supported - will cause problems! TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported"); break; case '\0': TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly " "terminated by end of string"); return c; default: break; } if (intConversion && precisionSet && !widthSet) { // "precision" for integers gives the minimum number of digits (to be // padded with zeros on the left). This isn't really supported by the // iostreams, but we can approximately simulate it with the width if // the width isn't otherwise used. out.width(out.precision() + widthExtra); out.setf(std::ios::internal, std::ios::adjustfield); out.fill('0'); } return c+1; } //------------------------------------------------------------------------------ inline void formatImpl(std::ostream& out, const char* fmt, const detail::FormatArg* args, int numArgs) { // Saved stream state std::streamsize origWidth = out.width(); std::streamsize origPrecision = out.precision(); std::ios::fmtflags origFlags = out.flags(); char origFill = out.fill(); // "Positional mode" means all format specs should be of the form "%n$..." // with `n` an integer. We detect this in `streamStateFromFormat`. bool positionalMode = false; int argIndex = 0; while (true) { fmt = printFormatStringLiteral(out, fmt); if (*fmt == '\0') { if (!positionalMode && argIndex < numArgs) { TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string"); } break; } bool spacePadPositive = false; int ntrunc = -1; const char* fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt, args, argIndex, numArgs); // NB: argIndex may be incremented by reading variable width/precision // in `streamStateFromFormat`, so do the bounds check here. if (argIndex >= numArgs) { TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string"); return; } const FormatArg& arg = args[argIndex]; // Format the arg into the stream. if (!spacePadPositive) { arg.format(out, fmt, fmtEnd, ntrunc); } else { // The following is a special case with no direct correspondence // between stream formatting and the printf() behaviour. Simulate // it crudely by formatting into a temporary string stream and // munging the resulting string. std::ostringstream tmpStream; tmpStream.copyfmt(out); tmpStream.setf(std::ios::showpos); arg.format(tmpStream, fmt, fmtEnd, ntrunc); std::string result = tmpStream.str(); // allocates... yuck. for (size_t i = 0, iend = result.size(); i < iend; ++i) { if (result[i] == '+') result[i] = ' '; } out << result; } if (!positionalMode) ++argIndex; fmt = fmtEnd; } // Restore stream state out.width(origWidth); out.precision(origPrecision); out.flags(origFlags); out.fill(origFill); } } // namespace detail /// List of template arguments format(), held in a type-opaque way. /// /// A const reference to FormatList (typedef'd as FormatListRef) may be /// conveniently used to pass arguments to non-template functions: All type /// information has been stripped from the arguments, leaving just enough of a /// common interface to perform formatting as required. class FormatList { public: FormatList(detail::FormatArg* args, int N) : m_args(args), m_N(N) { } friend void vformat(std::ostream& out, const char* fmt, const FormatList& list); private: const detail::FormatArg* m_args; int m_N; }; /// Reference to type-opaque format list for passing to vformat() typedef const FormatList& FormatListRef; namespace detail { // Format list subclass with fixed storage to avoid dynamic allocation template class FormatListN : public FormatList { public: #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES template FormatListN(const Args&... args) : FormatList(&m_formatterStore[0], N), m_formatterStore { FormatArg(args)... } { static_assert(sizeof...(args) == N, "Number of args must be N"); } #else // C++98 version void init(int) {} # define TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR(n) \ \ template \ FormatListN(TINYFORMAT_VARARGS(n)) \ : FormatList(&m_formatterStore[0], n) \ { TINYFORMAT_ASSERT(n == N); init(0, TINYFORMAT_PASSARGS(n)); } \ \ template \ void init(int i, TINYFORMAT_VARARGS(n)) \ { \ m_formatterStore[i] = FormatArg(v1); \ init(i+1 TINYFORMAT_PASSARGS_TAIL(n)); \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR) # undef TINYFORMAT_MAKE_FORMATLIST_CONSTRUCTOR #endif FormatListN(const FormatListN& other) : FormatList(&m_formatterStore[0], N) { std::copy(&other.m_formatterStore[0], &other.m_formatterStore[N], &m_formatterStore[0]); } private: FormatArg m_formatterStore[N]; }; // Special 0-arg version - MSVC says zero-sized C array in struct is nonstandard template<> class FormatListN<0> : public FormatList { public: FormatListN() : FormatList(0, 0) {} }; } // namespace detail //------------------------------------------------------------------------------ // Primary API functions #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES /// Make type-agnostic format list from list of template arguments. /// /// The exact return type of this function is an implementation detail and /// shouldn't be relied upon. Instead it should be stored as a FormatListRef: /// /// FormatListRef formatList = makeFormatList( /*...*/ ); template detail::FormatListN makeFormatList(const Args&... args) { return detail::FormatListN(args...); } #else // C++98 version inline detail::FormatListN<0> makeFormatList() { return detail::FormatListN<0>(); } #define TINYFORMAT_MAKE_MAKEFORMATLIST(n) \ template \ detail::FormatListN makeFormatList(TINYFORMAT_VARARGS(n)) \ { \ return detail::FormatListN(TINYFORMAT_PASSARGS(n)); \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_MAKEFORMATLIST) #undef TINYFORMAT_MAKE_MAKEFORMATLIST #endif /// Format list of arguments to the stream according to the given format string. /// /// The name vformat() is chosen for the semantic similarity to vprintf(): the /// list of format arguments is held in a single function argument. inline void vformat(std::ostream& out, const char* fmt, FormatListRef list) { detail::formatImpl(out, fmt, list.m_args, list.m_N); } #ifdef TINYFORMAT_USE_VARIADIC_TEMPLATES /// Format list of arguments to the stream according to given format string. template void format(std::ostream& out, const char* fmt, const Args&... args) { vformat(out, fmt, makeFormatList(args...)); } /// Format list of arguments according to the given format string and return /// the result as a string. template std::string format(const char* fmt, const Args&... args) { std::ostringstream oss; format(oss, fmt, args...); return oss.str(); } /// Format list of arguments to std::cout, according to the given format string template void printf(const char* fmt, const Args&... args) { format(std::cout, fmt, args...); } template void printfln(const char* fmt, const Args&... args) { format(std::cout, fmt, args...); std::cout << '\n'; } #else // C++98 version inline void format(std::ostream& out, const char* fmt) { vformat(out, fmt, makeFormatList()); } inline std::string format(const char* fmt) { std::ostringstream oss; format(oss, fmt); return oss.str(); } inline void printf(const char* fmt) { format(std::cout, fmt); } inline void printfln(const char* fmt) { format(std::cout, fmt); std::cout << '\n'; } #define TINYFORMAT_MAKE_FORMAT_FUNCS(n) \ \ template \ void format(std::ostream& out, const char* fmt, TINYFORMAT_VARARGS(n)) \ { \ vformat(out, fmt, makeFormatList(TINYFORMAT_PASSARGS(n))); \ } \ \ template \ std::string format(const char* fmt, TINYFORMAT_VARARGS(n)) \ { \ std::ostringstream oss; \ format(oss, fmt, TINYFORMAT_PASSARGS(n)); \ return oss.str(); \ } \ \ template \ void printf(const char* fmt, TINYFORMAT_VARARGS(n)) \ { \ format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ } \ \ template \ void printfln(const char* fmt, TINYFORMAT_VARARGS(n)) \ { \ format(std::cout, fmt, TINYFORMAT_PASSARGS(n)); \ std::cout << '\n'; \ } TINYFORMAT_FOREACH_ARGNUM(TINYFORMAT_MAKE_FORMAT_FUNCS) #undef TINYFORMAT_MAKE_FORMAT_FUNCS #endif } // namespace tinyformat #endif // TINYFORMAT_H_INCLUDED svglite/src/Makevars.ucrt0000644000176200001440000000002514511442606015166 0ustar liggesusersPKG_LIBS = -lpng -lz svglite/src/devSVG.cpp0000644000176200001440000013046214534617707014404 0ustar liggesusers// (C) 2002 T Jake Luciani: SVG device, based on PicTex device // (C) 2008 Tony Plate: Line type support from RSVGTipsDevice package // (C) 2012 Matthieu Decorde: UTF-8 support, XML reserved characters and XML header // (C) 2015 RStudio (Hadley Wickham): modernisation & refactoring // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SvgStream.h" #include "utils.h" #include "tinyformat.h" typedef std::shared_ptr SvgStreamPtr; // SVG device metadata class SVGDesc { public: SvgStreamPtr stream; int pageno; bool is_inited; std::string clipid; // ID for the clip path bool is_clipping; double clipx0, clipx1, clipy0, clipy1; // Save the previous clip path to avoid duplication bool standalone; bool fix_text_size; double scaling; bool always_valid; const std::string file; cpp11::list system_aliases; cpp11::list user_aliases; const std::string webfonts; cpp11::strings ids; // Caches std::unordered_set clip_cache; unsigned int clip_cache_next_id; bool is_recording_clip; std::unordered_set mask_cache; unsigned int mask_cache_next_id; int current_mask; std::unordered_set pattern_cache; unsigned int pattern_cache_next_id; SVGDesc(SvgStreamPtr stream_, bool standalone_, cpp11::list aliases_, const std::string webfonts_, const std::string& file_, cpp11::strings ids_, bool fix_text_size_, double scaling_, bool always_valid_): stream(stream_), pageno(0), is_inited(false), is_clipping(false), clipx0(0), clipx1(0), clipy0(0), clipy1(0), standalone(standalone_), fix_text_size(fix_text_size_), scaling(scaling_), always_valid(always_valid_), file(file_), system_aliases(cpp11::as_cpp(aliases_["system"])), user_aliases(cpp11::as_cpp(aliases_["user"])), webfonts(webfonts_), ids(ids_), clip_cache_next_id(0), is_recording_clip(false), mask_cache_next_id(0), current_mask(-1), pattern_cache_next_id(0) { } void nextFile() { stream->finish(false); if (stream->is_file_stream()) { SvgStreamPtr newStream(new SvgStreamFile(file, pageno + 1, always_valid)); stream = newStream; } clipid.clear(); set_clipping(false); } void set_clipping(bool clip) { stream->set_clipping(clip); is_clipping = clip; } }; inline bool is_black(int col) { return (R_RED(col) == 0) && (R_GREEN(col) == 0) && (R_BLUE(col) == 0) && (R_ALPHA(col) == 255); } inline bool is_bold(int face) { return face == 2 || face == 4; } inline bool is_italic(int face) { return face == 3 || face == 4; } inline bool is_bolditalic(int face) { return face == 4; } inline bool is_symbol(int face) { return face == 5; } const static char encode_lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const static char pad_character = '='; inline std::string base64_encode(const std::uint8_t* buffer, size_t size) { std::string encoded_string; encoded_string.reserve(((size/3) + (size % 3 > 0)) * 4); std::uint32_t temp{}; int index = 0; for (size_t idx = 0; idx < size/3; idx++) { temp = buffer[index++] << 16; //Convert to big endian temp += buffer[index++] << 8; temp += buffer[index++]; encoded_string.append(1, encode_lookup[(temp & 0x00FC0000) >> 18]); encoded_string.append(1, encode_lookup[(temp & 0x0003F000) >> 12]); encoded_string.append(1, encode_lookup[(temp & 0x00000FC0) >> 6 ]); encoded_string.append(1, encode_lookup[(temp & 0x0000003F) ]); } switch (size % 3) { case 1: temp = buffer[index++] << 16; //Convert to big endian encoded_string.append(1, encode_lookup[(temp & 0x00FC0000) >> 18]); encoded_string.append(1, encode_lookup[(temp & 0x0003F000) >> 12]); encoded_string.append(2, pad_character); break; case 2: temp = buffer[index++] << 16; //Convert to big endian temp += buffer[index++] << 8; encoded_string.append(1, encode_lookup[(temp & 0x00FC0000) >> 18]); encoded_string.append(1, encode_lookup[(temp & 0x0003F000) >> 12]); encoded_string.append(1, encode_lookup[(temp & 0x00000FC0) >> 6 ]); encoded_string.append(1, pad_character); break; } return encoded_string; } static void png_memory_write(png_structp png_ptr, png_bytep data, png_size_t length) { std::vector *p = (std::vector*)png_get_io_ptr(png_ptr); p->insert(p->end(), data, data + length); } inline std::string raster_to_string(unsigned int *raster, int w, int h, double width, double height, bool interpolate) { h = h < 0 ? -h : h; w = w < 0 ? -w : w; bool resize = false; int w_fac = 1, h_fac = 1; std::vector raster_resize; if (!interpolate && double(w) < width) { resize = true; w_fac = std::ceil(width / w); } if (!interpolate && double(h) < height) { resize = true; h_fac = std::ceil(height / h); } if (resize) { int w_new = w * w_fac; int h_new = h * h_fac; raster_resize.reserve(w_new * h_new); for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { unsigned int val = raster[i * w + j]; for (int wrep = 0; wrep < w_fac; ++wrep) { raster_resize.push_back(val); } } for (int hrep = 1; hrep < h_fac; ++hrep) { raster_resize.insert(raster_resize.end(), raster_resize.end() - w_new, raster_resize.end()); } } raster = raster_resize.data(); w = w_new; h = h_new; } png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { return ""; } png_infop info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, (png_infopp)NULL); return ""; } if (setjmp(png_jmpbuf(png))) { png_destroy_write_struct(&png, &info); return ""; } png_set_IHDR( png, info, w, h, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); std::vector rows(h); for (int y = 0; y < h; ++y) { rows[y] = (uint8_t*)raster + y * w * 4; } std::vector buffer; png_set_rows(png, info, &rows[0]); png_set_write_fn(png, &buffer, png_memory_write, NULL); png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); png_destroy_write_struct(&png, &info); return base64_encode(buffer.data(), buffer.size()); } inline std::string find_alias_field(std::string family, cpp11::list& alias, const char* face, const char* field) { if (alias[face] != R_NilValue) { cpp11::list font(alias[face]); if (font[field] != R_NilValue) return cpp11::as_cpp(font[field]); } return std::string(); } inline std::string find_user_alias(std::string family, cpp11::list const& aliases, int face, const char* field) { std::string out; if (aliases[family.c_str()] != R_NilValue) { cpp11::list alias(aliases[family.c_str()]); if (is_bolditalic(face)) out = find_alias_field(family, alias, "bolditalic", field); else if (is_bold(face)) out = find_alias_field(family, alias, "bold", field); else if (is_italic(face)) out = find_alias_field(family, alias, "italic", field); else if (is_symbol(face)) out = find_alias_field(family, alias, "symbol", field); else out = find_alias_field(family, alias, "plain", field); } return out; } inline std::string find_system_alias(std::string family, cpp11::list const& aliases) { std::string out; if (aliases[family.c_str()] != R_NilValue) { cpp11::sexp alias = aliases[family.c_str()]; if (TYPEOF(alias) == STRSXP && Rf_length(alias) == 1) out = cpp11::as_cpp(alias); } return out; } inline std::string fontname(const char* family_, int face, cpp11::list const& system_aliases, cpp11::list const& user_aliases, FontSettings& font) { std::string family(family_); if (face == 5) family = "symbol"; else if (family == "") family = "sans"; std::string alias = find_system_alias(family, system_aliases); if (alias.empty()) { alias = find_user_alias(family, user_aliases, face, "name"); } if (!alias.empty()) { return alias; } std::string family_name = ""; family_name.resize(100); if (get_font_family(font.file, font.index, &family_name[0], 100)) { family_name.erase(family_name.find('\0')); return family_name; } return family; } inline std::string fontfile(const char* family_, int face, cpp11::list user_aliases) { std::string family(family_); if (face == 5) family = "symbol"; else if (family == "") family = "sans"; return find_user_alias(family, user_aliases, face, "file"); } inline FontSettings get_font_file(const char* family, int face, cpp11::list user_aliases) { const char* fontfamily = family; if (is_symbol(face)) { fontfamily = "symbol"; } else if (strcmp(family, "") == 0) { fontfamily = "sans"; } std::string alias = fontfile(fontfamily, face, user_aliases); if (!alias.empty()) { FontSettings result = {}; std::strncpy(result.file, alias.c_str(), PATH_MAX); result.index = 0; result.n_features = 0; return result; } return locate_font_with_features(fontfamily, is_italic(face), is_bold(face)); } inline void write_escaped(SvgStreamPtr stream, const char* text) { for(const char* cur = text; *cur != '\0'; ++cur) { switch(*cur) { case '&': (*stream) << "&"; break; case '<': (*stream) << "<"; break; case '>': (*stream) << ">"; break; default: (*stream) << *cur; } } } inline void write_attr_dbl(SvgStreamPtr stream, const char* attr, double value) { (*stream) << ' ' << attr << "='" << value << '\''; } inline void write_attr_str(SvgStreamPtr stream, const char* attr, const char* value) { (*stream) << ' ' << attr << "='" << value << '\''; } // Writing clip path attribute inline void write_attr_clip(SvgStreamPtr stream, std::string clipid) { if (clipid.empty()) return; (*stream) << " clip-path='url(#cp" << clipid << ")'"; } // Writing mask attribute inline void write_attr_mask(SvgStreamPtr stream, int mask) { if (mask < 0) return; (*stream) << " mask='url(#mask-" << mask << ")'"; } // Beginning of writing style attributes inline void write_style_begin(SvgStreamPtr stream) { (*stream) << " style='"; } // End of writing style attributes inline void write_style_end(SvgStreamPtr stream) { (*stream) << "'"; } // Writing style attributes related to colors inline void write_style_col(SvgStreamPtr stream, const char* attr, int col) { int alpha = R_ALPHA(col); if (alpha == 0) { (*stream) << attr << ": none;"; return; } else { (*stream) << tfm::format("%s: #%02X%02X%02X;", attr, R_RED(col), R_GREEN(col), R_BLUE(col)); if (alpha != 255) (*stream) << ' ' << attr << "-opacity: " << alpha / 255.0 << ';'; } } // Writing style attributes related to stroke inline void write_style_stroke(SvgStreamPtr stream, int col, bool first = false) { // Default is "stroke: #000000;" as declared in \n"; (*stream) << "\n"; (*stream) << "fill) != 0) { write_style_col(stream, "fill", gc->fill); } else { write_style_col(stream, "fill", dd->startfill); } write_style_end(stream); (*stream) << "/>\n"; // Initialise clipping - make sure the stored clipping is bogus to force // svg_clip to do its thing svgd->clipx0 = R_PosInf; svgd->clipy0 = R_NegInf; svgd->clipx1 = R_NegInf; svgd->clipy1 = R_PosInf; svgd->is_inited = true; svg_clip(0, dd->right, dd->bottom, 0, dd); svgd->stream->flush(); svgd->pageno++; } void svg_close(pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (svgd->is_inited) { svgd->stream->finish(true); } delete(svgd); } void svg_line(double x1, double y1, double x2, double y2, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited || svgd->is_recording_clip) { return; } SvgStreamPtr stream = svgd->stream; (*stream) << "current_mask); write_style_begin(stream); write_style_linetype(stream, gc, svgd->scaling, true); write_style_end(stream); (*stream) << " />\n"; stream->flush(); } void svg_poly(int n, double *x, double *y, int filled, const pGEcontext gc, pDevDesc dd, const char* node_name) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (n == 0 || !svgd->is_inited || (!filled && svgd->is_recording_clip)) { return; } SvgStreamPtr stream = svgd->stream; if (svgd->is_recording_clip) { (*stream) << "M " << x[0] << ',' << y[0] << ' '; for (int i = 1; i < n; i++) { (*stream) << "L " << x[i] << ',' << y[i] << ' '; } stream->put('Z'); return; } (*stream) << "<" << node_name << " points='"; for (int i = 0; i < n; i++) { (*stream) << x[i] << ',' << y[i] << ' '; } stream->put('\''); write_attr_mask(stream, svgd->current_mask); write_style_begin(stream); write_style_linetype(stream, gc, svgd->scaling, true); if (filled) { write_style_fill(stream, gc); } write_style_end(stream); (*stream) << " />\n"; stream->flush(); } void svg_polyline(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd) { svg_poly(n, x, y, 0, gc, dd, "polyline"); } void svg_polygon(int n, double *x, double *y, const pGEcontext gc, pDevDesc dd) { svg_poly(n, x, y, 1, gc, dd, "polygon"); } void svg_path(double *x, double *y, int npoly, int *nper, Rboolean winding, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited) { return; } SvgStreamPtr stream = svgd->stream; // Create path data if (!svgd->is_recording_clip) { (*stream) << "put('Z'); } if (svgd->is_recording_clip) { return; } // Finish path data stream->put('\''); write_attr_mask(stream, svgd->current_mask); write_style_begin(stream); // Specify fill rule write_style_str(stream, "fill-rule", winding ? "nonzero" : "evenodd", true); write_style_fill(stream, gc); write_style_linetype(stream, gc, svgd->scaling); write_style_end(stream); (*stream) << " />\n"; stream->flush(); } double svg_strwidth(const char *str, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; FontSettings font = get_font_file(gc->fontfamily, gc->fontface, svgd->user_aliases); double width = 0.0; int error = string_width(str, font.file, font.index, gc->ps * gc->cex * svgd->scaling, 1e4, 1, &width); if (error != 0) { width = 0.0; } return width * 72. / 1e4; } void svg_rect(double x0, double y0, double x1, double y1, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited) { return; } SvgStreamPtr stream = svgd->stream; if (svgd->is_recording_clip) { (*stream) << "M " << x0 << ',' << y0 << " L " << x0 << ',' << y1 << " L " << x1 << ',' << y1 << " L " << x1 << ',' << y0; stream->put('Z'); return; } // x and y give top-left position (*stream) << "current_mask); write_style_begin(stream); write_style_linetype(stream, gc, svgd->scaling, true); write_style_fill(stream, gc); write_style_end(stream); (*stream) << " />\n"; stream->flush(); } void svg_circle(double x, double y, double r, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited) { return; } SvgStreamPtr stream = svgd->stream; if (svgd->is_recording_clip) { (*stream) << "M " << x-r << ',' << y << " a " << r << ',' << r << " 0 1,1 " << r*2 << ",0 " << " a " << r << ',' << r << " 0 1,1 " << -r*2 << ",0 "; stream->put('Z'); return; } (*stream) << "current_mask); write_style_begin(stream); write_style_linetype(stream, gc, svgd->scaling, true); write_style_fill(stream, gc); write_style_end(stream); (*stream) << " />\n"; stream->flush(); } void svg_text(double x, double y, const char *str, double rot, double hadj, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited || svgd->is_recording_clip) { return; } SvgStreamPtr stream = svgd->stream; (*stream) << "cex * gc->ps; if (hadj == 0.5) { write_attr_str(stream, "text-anchor", "middle"); } else if (hadj == 1) { write_attr_str(stream, "text-anchor", "end"); } write_attr_mask(stream, svgd->current_mask); write_style_begin(stream); write_style_fontsize(stream, fontsize * svgd->scaling, true); FontSettings font_info = get_font_file(gc->fontfamily, gc->fontface, svgd->user_aliases); int weight = get_font_weight(font_info.file, font_info.index); if (weight != 400) { if (weight == 700) { write_style_str(stream, "font-weight", "bold"); } else { write_style_int(stream, "font-weight", weight); } } if (is_italic(gc->fontface)) write_style_str(stream, "font-style", "italic"); if (!is_black(gc->col)) write_style_col(stream, "fill", gc->col); std::string font = fontname(gc->fontfamily, gc->fontface, svgd->system_aliases, svgd->user_aliases, font_info); font = "\"" + font + "\""; write_style_str(stream, "font-family", font.c_str()); if (font_info.n_features > 0) { (*stream) << " font-feature-settings: "; for (int i = 0; i < font_info.n_features; ++i) { std::string feature = ""; feature += font_info.features[i].feature[0]; feature += font_info.features[i].feature[1]; feature += font_info.features[i].feature[2]; feature += font_info.features[i].feature[3]; (*stream) << "\"" << feature << "\" " << font_info.features[i].setting; (*stream) << (i == font_info.n_features - 1 ? ";" : ","); } } write_style_end(stream); if (svgd->fix_text_size) { double width = svg_strwidth(str, gc, dd); (*stream) << " textLength='" << width << "px'"; (*stream) << " lengthAdjust='spacingAndGlyphs'"; } stream->put('>'); write_escaped(stream, str); (*stream) << ""; stream->put('\n'); stream->flush(); } void svg_size(double *left, double *right, double *bottom, double *top, pDevDesc dd) { *left = dd->left; *right = dd->right; *bottom = dd->bottom; *top = dd->top; } void svg_raster(unsigned int *raster, int w, int h, double x, double y, double width, double height, double rot, Rboolean interpolate, const pGEcontext gc, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (!svgd->is_inited || svgd->is_recording_clip) { return; } SvgStreamPtr stream = svgd->stream; if (height < 0) height = -height; std::string base64_str = raster_to_string(raster, w, h, width, height, interpolate); (*stream) << "current_mask); if (!interpolate) { write_attr_str(stream, "image-rendering", "pixelated"); } if( rot != 0 ){ (*stream) << tfm::format(" transform='rotate(%0.0f,%.2f,%.2f)'", -1.0 * rot, x, y); } (*stream) << " xlink:href='data:image/png;base64," << base64_str << '\''; (*stream) << "/>"; stream->put('\n'); stream->flush(); } SEXP svg_set_pattern(SEXP pattern, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (Rf_isNull(pattern)) { return Rf_ScalarInteger(-1); } int key = svgd->pattern_cache_next_id; svgd->pattern_cache_next_id++; #if R_GE_version >= 13 SvgStreamPtr stream = svgd->stream; std::string extend = "spreadMethod="; // Cache current clipping and break out of clipping group bool was_clipping = svgd->is_clipping; std::string old_clipid = svgd->clipid; double clipx0 = svgd->clipx0; double clipx1 = svgd->clipx1; double clipy0 = svgd->clipy0; double clipy1 = svgd->clipy1; if (was_clipping) { (*stream) << "\n"; } svgd->set_clipping(false); (*stream) << "\n"; switch(R_GE_patternType(pattern)) { case R_GE_linearGradientPattern: switch(R_GE_linearGradientExtend(pattern)) { case R_GE_patternExtendNone: ; case R_GE_patternExtendPad: extend += "'pad'"; break; case R_GE_patternExtendReflect: extend += "'reflect'"; break; case R_GE_patternExtendRepeat: extend += "'repeat"; break; } (*stream) << "\n"; for (int i = 0; i < R_GE_linearGradientNumStops(pattern); ++i) { int col = R_GE_linearGradientColour(pattern, i); (*stream) << " \n"; } (*stream) << "\n"; break; case R_GE_radialGradientPattern: switch(R_GE_radialGradientExtend(pattern)) { case R_GE_patternExtendNone: ; case R_GE_patternExtendPad: extend += "'pad'"; break; case R_GE_patternExtendReflect: extend += "'reflect'"; break; case R_GE_patternExtendRepeat: extend += "'repeat"; break; } (*stream) << "\n"; for (int i = 0; i < R_GE_radialGradientNumStops(pattern); ++i) { int col = R_GE_radialGradientColour(pattern, i); (*stream) << " \n"; } (*stream) << "\n"; break; case R_GE_tilingPattern: (*stream) << "\n\n"; int old_mask = svgd->current_mask; svgd->current_mask = -1; SEXP R_fcall = PROTECT(Rf_lang1(R_GE_tilingPatternFunction(pattern))); Rf_eval(R_fcall, R_GlobalEnv); UNPROTECT(1); svgd->current_mask = old_mask; if (svgd->is_clipping) { (*stream) << "\n"; } svgd->set_clipping(false); (*stream) << "\n\n"; break; } (*stream) << "\n"; // Resume old clipping if it was happening if (was_clipping) { (*stream) << "clipid = old_clipid; svgd->clipx0 = clipx0; svgd->clipx1 = clipx1; svgd->clipy0 = clipy0; svgd->clipy1 = clipy1; write_attr_clip(stream, svgd->clipid); (*stream) << ">\n"; svgd->set_clipping(true); } #endif svgd->pattern_cache.insert(key); return Rf_ScalarInteger(key); } void svg_release_pattern(SEXP ref, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (Rf_isNull(ref)) { svgd->pattern_cache.clear(); return; } unsigned int key = INTEGER(ref)[0]; auto it = svgd->pattern_cache.find(key); // Check if path exists if (it != svgd->pattern_cache.end()) { svgd->pattern_cache.erase(it); } } SEXP svg_set_clip_path(SEXP path, SEXP ref, pDevDesc dd) { int key; if (Rf_isNull(path)) { return Rf_ScalarInteger(-1); } SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (Rf_isNull(ref)) { key = svgd->clip_cache_next_id; svgd->clip_cache_next_id++; } else { key = INTEGER(ref)[0]; if (key < 0) { return Rf_ScalarInteger(key); } } SvgStreamPtr stream = svgd->stream; if (svgd->is_clipping) { (*stream) << "\n"; } auto clip_cache_iter = svgd->clip_cache.find(key); // Check if path exists if (clip_cache_iter == svgd->clip_cache.end()) { bool new_clip_is_even_odd = false; #if R_GE_version >= 15 new_clip_is_even_odd = R_GE_clipPathFillRule(path) == R_GE_evenOddRule; #endif (*stream) << "\n"; (*stream) << " \n"; (*stream) << " \n \n"; (*stream) << "\n"; svgd->clip_cache.insert(key); } svgd->clipid = "-" + std::to_string(key); svgd->clipx0 = 0; svgd->clipx1 = 0; svgd->clipy0 = 0; svgd->clipy1 = 0; (*stream) << "clipid); (*stream) << ">\n"; svgd->set_clipping(true); return Rf_ScalarInteger(key); } void svg_release_clip_path(SEXP ref, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (Rf_isNull(ref)) { svgd->clip_cache.clear(); return; } int key = INTEGER(ref)[0]; if (key < 0) { return; } auto it = svgd->clip_cache.find(key); // Check if path exists if (it != svgd->clip_cache.end()) { svgd->clip_cache.erase(it); } } SEXP svg_set_mask(SEXP path, SEXP ref, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; int key; if (Rf_isNull(path)) { svgd->current_mask = -1; return Rf_ScalarInteger(-1); } if (Rf_isNull(ref)) { key = svgd->mask_cache_next_id; svgd->mask_cache_next_id++; } else { key = INTEGER(ref)[0]; if (key < 0) { svgd->current_mask = -1; return Rf_ScalarInteger(key); } } SvgStreamPtr stream = svgd->stream; auto mask_cache_iter = svgd->mask_cache.find(key); // Check if path exists if (mask_cache_iter == svgd->mask_cache.end()) { // Cache current clipping and break out of clipping group bool was_clipping = svgd->is_clipping; std::string old_clipid = svgd->clipid; double clipx0 = svgd->clipx0; double clipx1 = svgd->clipx1; double clipy0 = svgd->clipy0; double clipy1 = svgd->clipy1; if (was_clipping) { (*stream) << "\n"; } svgd->set_clipping(false); (*stream) << "\n"; (*stream) << " \n"; SEXP R_fcall = PROTECT(Rf_lang1(path)); Rf_eval(R_fcall, R_GlobalEnv); UNPROTECT(1); // Clipping may have happened above. End it before terminating mask if (svgd->is_clipping) { (*stream) << "\n"; } svgd->set_clipping(false); (*stream) << " \n"; (*stream) << "\n"; // Resume old clipping if it was happening if (was_clipping) { (*stream) << "clipid = old_clipid; svgd->clipx0 = clipx0; svgd->clipx1 = clipx1; svgd->clipy0 = clipy0; svgd->clipy1 = clipy1; write_attr_clip(stream, svgd->clipid); (*stream) << ">\n"; svgd->set_clipping(true); } svgd->mask_cache.insert(key); } svgd->current_mask = key; return Rf_ScalarInteger(key); } void svg_release_mask(SEXP ref, pDevDesc dd) { SVGDesc *svgd = (SVGDesc*) dd->deviceSpecific; if (Rf_isNull(ref)) { svgd->mask_cache.clear(); return; } unsigned int key = INTEGER(ref)[0]; auto it = svgd->mask_cache.find(key); // Check if path exists if (it != svgd->mask_cache.end()) { svgd->mask_cache.erase(it); } } SEXP svg_capabilities(SEXP capabilities) { #if R_GE_version >= 15 // Pattern support SEXP pat = PROTECT(Rf_allocVector(INTSXP, 3)); INTEGER(pat)[0] = R_GE_linearGradientPattern; INTEGER(pat)[1] = R_GE_radialGradientPattern; INTEGER(pat)[2] = R_GE_tilingPattern; SET_VECTOR_ELT(capabilities, R_GE_capability_patterns, pat); UNPROTECT(1); // Clipping path support SET_VECTOR_ELT(capabilities, R_GE_capability_clippingPaths, Rf_ScalarInteger(1)); // Mask support SEXP masks = PROTECT(Rf_allocVector(INTSXP, 2)); INTEGER(masks)[0] = R_GE_alphaMask; INTEGER(masks)[1] = R_GE_luminanceMask; SET_VECTOR_ELT(capabilities, R_GE_capability_masks, masks); UNPROTECT(1); // Group composition SET_VECTOR_ELT(capabilities, R_GE_capability_compositing, Rf_ScalarInteger(0)); // Group transformation SET_VECTOR_ELT(capabilities, R_GE_capability_transformations, Rf_ScalarInteger(0)); // Path stroking and filling SET_VECTOR_ELT(capabilities, R_GE_capability_paths, Rf_ScalarInteger(0)); #endif return capabilities; } pDevDesc svg_driver_new(SvgStreamPtr stream, int bg, double width, double height, double pointsize, bool standalone, cpp11::list& aliases, const std::string& webfonts, const std::string& file, cpp11::strings id, bool fix_text_size, double scaling, bool always_valid) { pDevDesc dd = (DevDesc*) calloc(1, sizeof(DevDesc)); if (dd == NULL) return dd; dd->startfill = bg; dd->startcol = R_RGB(0, 0, 0); dd->startps = pointsize; dd->startlty = 0; dd->startfont = 1; dd->startgamma = 1; // Callbacks dd->activate = NULL; dd->deactivate = NULL; dd->close = svg_close; dd->clip = svg_clip; dd->size = svg_size; dd->newPage = svg_new_page; dd->line = svg_line; dd->text = svg_text; dd->strWidth = svg_strwidth; dd->rect = svg_rect; dd->circle = svg_circle; dd->polygon = svg_polygon; dd->polyline = svg_polyline; dd->path = svg_path; dd->mode = NULL; dd->metricInfo = svg_metric_info; dd->cap = NULL; dd->raster = svg_raster; #if R_GE_version >= 13 dd->setPattern = svg_set_pattern; dd->releasePattern = svg_release_pattern; dd->setClipPath = svg_set_clip_path; dd->releaseClipPath = svg_release_clip_path; dd->setMask = svg_set_mask; dd->releaseMask = svg_release_mask; #endif // UTF-8 support dd->wantSymbolUTF8 = (Rboolean) 1; dd->hasTextUTF8 = (Rboolean) 1; dd->textUTF8 = svg_text; dd->strWidthUTF8 = svg_strwidth; // Screen Dimensions in pts dd->left = 0; dd->top = 0; dd->right = width * 72; dd->bottom = height * 72; // Magic constants copied from other graphics devices // nominal character sizes in pts dd->cra[0] = 0.9 * pointsize * scaling; dd->cra[1] = 1.2 * pointsize * scaling; // character alignment offsets dd->xCharOffset = 0.4900; dd->yCharOffset = 0.3333; dd->yLineBias = 0.2; // inches per pt dd->ipr[0] = 1.0 / (72.0 * scaling); dd->ipr[1] = 1.0 / (72.0 * scaling); // Capabilities #if R_GE_version >= 15 dd->capabilities = svg_capabilities; #endif dd->canClip = TRUE; #if R_GE_version >= 14 dd->deviceClip = TRUE; #endif dd->canHAdj = 1; dd->canChangeGamma = FALSE; dd->displayListOn = FALSE; dd->haveTransparency = 2; dd->haveTransparentBg = 2; #if R_GE_version >= 13 dd->deviceVersion = 15; //R_GE_group; #endif dd->deviceSpecific = new SVGDesc(stream, standalone, aliases, webfonts, file, id, fix_text_size, scaling, always_valid); return dd; } void makeDevice(SvgStreamPtr stream, std::string bg_, double width, double height, double pointsize, bool standalone, cpp11::list& aliases, const std::string& webfonts, const std::string& file, cpp11::strings id, bool fix_text_size, double scaling, bool always_valid) { int bg = R_GE_str2col(bg_.c_str()); R_GE_checkVersionOrDie(R_GE_version); R_CheckDeviceAvailable(); BEGIN_SUSPEND_INTERRUPTS { pDevDesc dev = svg_driver_new(stream, bg, width, height, pointsize, standalone, aliases, webfonts, file, id, fix_text_size, scaling, always_valid); if (dev == NULL) cpp11::stop("Failed to start SVG device"); pGEDevDesc dd = GEcreateDevDesc(dev); GEaddDevice2(dd, "devSVG"); GEinitDisplayList(dd); } END_SUSPEND_INTERRUPTS; } [[cpp11::register]] bool svglite_(std::string file, std::string bg, double width, double height, double pointsize, bool standalone, cpp11::list aliases, std::string webfonts, cpp11::strings id, bool fix_text_size, double scaling, bool always_valid) { SvgStreamPtr stream(new SvgStreamFile(file, 1, always_valid)); makeDevice(stream, bg, width, height, pointsize, standalone, aliases, webfonts, file, id, fix_text_size, scaling, always_valid); return true; } [[cpp11::register]] cpp11::external_pointer svgstring_(cpp11::environment env, std::string bg, double width, double height, double pointsize, bool standalone, cpp11::list aliases, std::string webfonts, cpp11::strings id, bool fix_text_size, double scaling) { SvgStreamPtr stream(new SvgStreamString(env)); makeDevice(stream, bg, width, height, pointsize, standalone, aliases, webfonts, "", id, fix_text_size, scaling, true); SvgStreamString* strstream = static_cast(stream.get()); return {strstream->string_src(), false}; } [[cpp11::register]] std::string get_svg_content(cpp11::external_pointer p) { p->flush(); std::string svgstr = p->str(); // If the current SVG is empty, we also make the string empty // Otherwise append "" to make it a valid SVG if(!svgstr.empty()) { svgstr.append("\n"); } return svgstr; } svglite/src/cpp11.cpp0000644000176200001440000000610214164532276014157 0ustar liggesusers// Generated by cpp11: do not edit by hand // clang-format off #include "svglite_types.h" #include "cpp11/declarations.hpp" #include // devSVG.cpp bool svglite_(std::string file, std::string bg, double width, double height, double pointsize, bool standalone, cpp11::list aliases, std::string webfonts, cpp11::strings id, bool fix_text_size, double scaling, bool always_valid); extern "C" SEXP _svglite_svglite_(SEXP file, SEXP bg, SEXP width, SEXP height, SEXP pointsize, SEXP standalone, SEXP aliases, SEXP webfonts, SEXP id, SEXP fix_text_size, SEXP scaling, SEXP always_valid) { BEGIN_CPP11 return cpp11::as_sexp(svglite_(cpp11::as_cpp>(file), cpp11::as_cpp>(bg), cpp11::as_cpp>(width), cpp11::as_cpp>(height), cpp11::as_cpp>(pointsize), cpp11::as_cpp>(standalone), cpp11::as_cpp>(aliases), cpp11::as_cpp>(webfonts), cpp11::as_cpp>(id), cpp11::as_cpp>(fix_text_size), cpp11::as_cpp>(scaling), cpp11::as_cpp>(always_valid))); END_CPP11 } // devSVG.cpp cpp11::external_pointer svgstring_(cpp11::environment env, std::string bg, double width, double height, double pointsize, bool standalone, cpp11::list aliases, std::string webfonts, cpp11::strings id, bool fix_text_size, double scaling); extern "C" SEXP _svglite_svgstring_(SEXP env, SEXP bg, SEXP width, SEXP height, SEXP pointsize, SEXP standalone, SEXP aliases, SEXP webfonts, SEXP id, SEXP fix_text_size, SEXP scaling) { BEGIN_CPP11 return cpp11::as_sexp(svgstring_(cpp11::as_cpp>(env), cpp11::as_cpp>(bg), cpp11::as_cpp>(width), cpp11::as_cpp>(height), cpp11::as_cpp>(pointsize), cpp11::as_cpp>(standalone), cpp11::as_cpp>(aliases), cpp11::as_cpp>(webfonts), cpp11::as_cpp>(id), cpp11::as_cpp>(fix_text_size), cpp11::as_cpp>(scaling))); END_CPP11 } // devSVG.cpp std::string get_svg_content(cpp11::external_pointer p); extern "C" SEXP _svglite_get_svg_content(SEXP p) { BEGIN_CPP11 return cpp11::as_sexp(get_svg_content(cpp11::as_cpp>>(p))); END_CPP11 } extern "C" { static const R_CallMethodDef CallEntries[] = { {"_svglite_get_svg_content", (DL_FUNC) &_svglite_get_svg_content, 1}, {"_svglite_svglite_", (DL_FUNC) &_svglite_svglite_, 12}, {"_svglite_svgstring_", (DL_FUNC) &_svglite_svgstring_, 11}, {NULL, NULL, 0} }; } extern "C" attribute_visible void R_init_svglite(DllInfo* dll){ R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); R_forceSymbols(dll, TRUE); } svglite/vignettes/0000755000176200001440000000000014534626740013752 5ustar liggesuserssvglite/vignettes/fonts.Rmd0000644000176200001440000001367114357213722015552 0ustar liggesusers--- title: "Specifying fonts" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Specifying fonts} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r, echo = FALSE, message = FALSE} knitr::opts_chunk$set(collapse = T, comment = "#>") library("svglite") ``` svglite produces SVG files containing plain text but fonts are still important for plot generation and rendering. Fonts are used during SVG generation to figure out the metrics of graphical elements. The font name is then recorded in the `font-family` property of text anchors so that SVG renderers know what fonts to use. svglite does try to ensure a consistent figure rendering even when fonts are not available at the time of rendering (by supplying the [textLength](https://www.w3.org/TR/SVG/text.html#TextElementTextLengthAttribute) SVG text attribute). However, the text may look slightly distorted when a fallback font is used. This means that for optimal display, the font must be available on both the computer used to create the svg, and the computer used to render the svg. The defaults are fonts that are available on almost all systems: there may be small differences between them, but they are unlikely to cause problems in most causes. | R family | Font on Windows | Font on Unix | |----------|--------------------|--------------| | `sans` | Arial | Arial | | `serif` | Times New Roman | Times | | `mono` | Courier | Courier | | `symbol` | Standard Symbols L | Symbol | One downside to these default fonts is that they do not have good coverage of characters for non-latin alphabets. This can be fixed by using the arguments `system_fonts` and `user_fonts` which provide control over which fonts to use during SVG generation and rendering. ## System font aliases `system_fonts` takes a named list of font families as argument. The names typically correspond to standard R faces but they can also alias non-standard families (though this is less useful): ```{r, eval=FALSE} fonts <- list( sans = "Helvetica", mono = "Consolas", `Times New Roman` = "DejaVu Serif" ) ss <- svgstring(system_fonts = fonts) plot(1:10) text(0.8, 0.8, "Some text", family = "mono") text(0.2, 0.2, "Other text", family = "Times New Roman") dev.off() ss() ``` If you need support for non-latin characters, choose fonts with good Unicode coverage. "Arial Unicode MS" is a sans serif font with good coverage that is available on macOS and Windows systems (on the latter, only if MS Office is installed). Note that this font does not support kerning and has no bold or italic faces. ```{r, eval=FALSE} svglite("Rplots.svg", system_fonts = list(sans = "Arial Unicode MS")) plot.new() text(0.5, 0.5, "正規分布") dev.off() ``` The [Noto fontset](https://fonts.google.com/noto) provided by Google as well as the [Han Sans family](https://github.com/adobe-fonts/source-han-sans) by Adobe have excellent coverage but may not be available at the time of rendering. This can be a concern if you distribute the SVG files on the Internet. ## User font aliases In addition to system fonts, you can also provide fonts that are not necessarily installed on the system (i.e., fonts that live in user space). The main reason to do this is to generate reproducible SVG files as different platforms can have different versions of a font and thus produce different text metrics. The `user_fonts` arguments takes either paths to font files, fonts from the `fontquiver` package, or a list that specifies the alias. Whereas `system_fonts` gets a named list of families as argument, `user_fonts` takes a named tree of lists of families (`sans`, `serif`, `mono` and `symbol`) and faces (`plain`, `italic`, `bold`, `bolditalic`, `symbol`): ```{r, eval=FALSE} # Using ttf files from fontquiver here, but it could be any ttf some_file <- fontquiver::font("Liberation", "Sans", "Regular")$ttf other_file <- fontquiver::font("Liberation", "Sans", "Italic")$ttf serif_file <- fontquiver::font("Liberation", "serif", "Italic")$ttf # The outer named list contains families while the inner named list # contains faces: fonts <- list( sans = list( plain = some_file, italic = other_file ), serif = list(plain = serif_file) ) ss <- svglite("plot.svg", user_fonts = fonts) plot.new() text(0.5, 0.5, "Sans Plain text") text(0.2, 0.2, "Sans Italic text", font = 3) text(0.8, 0.8, "Serif text", family = "serif") dev.off() ``` You can also control which font gets written in the `font-family` fields of SVGs by supplying a list containing `alias` and `file` elements: ```{r, eval=FALSE} file_with_alias <- list(alias = "Foobar Font", file = other_file) fonts <- list(sans = list(plain = file_with_alias)) ss <- svgstring(user_fonts = fonts) plot(1:10) text(0.5, 0.5, "Sans text") dev.off() ss() ``` `fontquiver` fonts are particularly useful for creating reproducible SVG files. The `vdiffr` package uses svglite with fontquiver fonts to create visual unit tests reliably across platforms. The Liberation fontset is appropriate for this usage because it features all 12 combinations of standard R families and faces. In addition fontquiver provides Symbola for the symbol font. The function `fontquiver::font_families()` produces a list with the appropriate structure and can be directly supplied to svglite: ```{r, eval=FALSE} fonts <- fontquiver::font_families("Liberation") fonts$symbol$symbol <- fontquiver::font_symbol("Symbola") str(fonts, 2) svglite("reproducible.svg", user_fonts = fonts) plot(1:10) dev.off() ``` ## Debugging font matching The systemfonts package is used to match font family names to fonts installed on the system. systemfonts will always return a valid font, but if the requested font is badly misspelled or missing, a default will be returned. To test if the expected font is matched you can use the `match_font()` and `font_info()` functions from systemfonts: ```{r} systemfonts::match_font("Helvetica") systemfonts::font_info("Helvetica", bold = TRUE) ``` svglite/vignettes/scaling.Rmd0000644000176200001440000001570614176711656016052 0ustar liggesusers--- title: "Scaling Issues" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Scaling Issues} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ## Scaling SVG outputs The SVG files produced by svglite do not include `width` and `height` properties. This is a deliberate choice intended to make it easier to fit fluidly a SVG figure to its enclosing container. The scaling straightforward but requires some understanding of the `viewBox` SVG attribute which is included in all SVGs produced by svglite. This property defines the aspect ratio of the plot (as well as a *user coordinate system*, see next section). ### Fluid scaling The viewBox is determined by the `width` and `height` arguments of svglite's device functions (with 10'' x 8'' the default). Although those dimensions are supplied in inches, the viewBox's user coordinate system is completely unit agnostic. The main effect is thus to determine an aspect ratio. Since dimensions are not provided, the dimensions of the enclosing container are used instead and the SVG is rescaled to fit the container (although Internet Explorer currently requires some CSS tricks to get this behaviour, see ). Aspect ratio is preserved by default when the figure is scaled up or down. The details of how the aspect ratio is preserved can be adjusted in multiple ways via the `preserveAspectRatio` attribute. See for more information about this property. Other useful resource: ### Natural scaling Another strategy is needed in order to scale the figure to make the text within the SVG consistent with the text in the surrounding web page. That could be useful, for instance, to create a consistent appearance in an HTML presentation. Since the user coordinate system defined by the viewBox is unitless, we need to map the figure to its natural dimensions. This will ensure a correspondence between the scale of the figure and that of the web page. As mentioned above, the natural scale of svglite's figures is in points and is determined by the `width` and `height` arguments that you supply to the device functions (10'' x 8'' being the default). Although those dimensions are specified in inches, the coordinate system is scaled in points. Counting 72 points per inch, the default SVG surface is thus 720 x 576 pt. Note that the CSS standard defines 12pt to be equal to 16px, the default size of text in most browsers. Since 12pt is the default text size in svglite as well, a SVG scaled to its natural dimensions will appear seamless with web text of 16px. If the text in your web page has another size, you will have to compute a scale factor and adjust the dimensions of the SVG accordingly. To sum up, displaying a plot according to its natural dimensions requires providing the user agent with information about what the lengths defined within the SVG actually mean. There are several ways to achieve this. First you can edit the SVG and enclose it in another pair of `` tags that defines `height` and `width`. The root `` element determines the final dimensions of the figure. A second way is to enclose the figure in a `
` tag with appropriate dimensions and let the SVG figure rescale itself to that container (cf. the section on fluid scaling): ```html
``` Finally, you can directly specify the dimensions in the `` or `` tag that is embedding the figure. Note that the dimension attributes of those tags do not accept arbitrary units, so you will have to supply the dimensions in pixels. Just multiply the width and height measured in points with a factor of 16/12: ```html ``` ## Internal notes ### Device scaling As other graphics devices, svglite is scaled in big points (1/72 inch) rather than pica points (1/72.27 inch). Note that in LaTeX and in the `grid` graphics system on which ggplot2 is based, points refer to pica points. Big points are denoted in LaTeX by `bp` and in CSS by `pt`. We use the latter notation. See for some historical background about these units. The conversion between device units and physical dimensions is determined by the DevDesc parameter `ipr`. IPR stands for inches per raster (native device coordinates are sometimes called rasters in R terminology) and is set to 1/72 in svglite. The device's physical dimensions are set by the following DevDesc parameters (with `width` and `height` the plot dimensions set by the user in inches): | Parameter | Value | |-----------|---------------| | `left` | `0` | | `top` | `0` | | `right` | `width * 72` | | `bottom` | `height * 72` | A default svglite plot surface is thus 720 x 576 pt. ### Scaling of graphical elements It is conventional for the fundamental line width (`lwd = 1`) to correspond to a line width of 1/96 inch and svglite obeys this convention. Also, like other R graphics devices, svglite interprets all point sizes directly as big points (e.g. the `ps` graphical parameter and the `fontsize` argument of device functions). The default font size is 12pt. Text metrics are computed by systemfonts, which uses freetype to extract metrics for each glyph and calculate string dimensions from that. Text metrics are calculated at 1000dpi based on `cex * ps` font size. The Base graphics system also makes use of the obscure `cra` parameter and its relatives (`cin`, `cxy`, and `csi`). `cra` serves as a crude measure for a default character height and width for the default fontsize provided when the device is called (12pt in svgilte). The main effect of this parameter (more specifically, the height component) is to change the relationship between the margin parameters `mar`/`mai` and `oma`/`omi`. The margins `mar` and `oma` are specified in line units and character height is used as a measure of line spacing to convert margins measured in lines to physical margins. As in other devices, `cra[0]` is set to `0.9 * pointsize` and `cra[1]` to `1.2 * pointsize`. These parameters are completely unused in the Grid graphics system. ### SVG output The SVG output sets up a viewBox (a user coordinate system) with values scaled in big points. **viewBox**: The width and height are set to `dd->right` and `dd->bottom` respectively (these values are determined by the user-supplied figure `width` and `height`). **Line width**: `1 lwd` should equal 1/96 inch. svglite gets values scaled in device coordinates (big points), so the line width is multiplied by 72/96. **Text**: gdtools returns metrics scaled in big points so no transformation is needed. We do need to add `px` units to work around a rendering bug in Firefox. Note that when a viewBox is set up, a pixel equals one unit in the user coordinate system and thus actually represents a big point. svglite/R/0000755000176200001440000000000014534574524012145 5ustar liggesuserssvglite/R/inlineSVG.R0000644000176200001440000000445214013470260014113 0ustar liggesusers#' Run plotting code and view svg in RStudio Viewer or web browser. #' #' This is useful primarily for testing. Requires the \code{htmltools} #' package. #' #' @param code Plotting code to execute. #' @param ... Other arguments passed on to \code{\link{svglite}}. #' @keywords internal #' @export #' @examples #' if (interactive() && require("htmltools")) { #' htmlSVG(plot(1:10)) #' htmlSVG(hist(rnorm(100))) #' } htmlSVG <- function(code, ...) { svg <- inlineSVG(code, ...) htmltools::browsable( htmltools::HTML(svg) ) } #' Run plotting code and return svg #' #' This is useful primarily for testing. Requires the \code{xml2} package. #' #' @return A \code{xml2::xml_document} object. #' @inheritParams htmlSVG #' @inheritParams svglite #' @keywords internal #' @export #' @examples #' if (require("xml2")) { #' x <- xmlSVG(plot(1, axes = FALSE)) #' x #' xml_find_all(x, ".//text") #' } xmlSVG <- function(code, ..., standalone = FALSE, height = 7, width = 7) { plot <- inlineSVG(code, ..., standalone = standalone, height = height, width = width ) xml2::read_xml(plot) } #' Run plotting code and open svg in OS/system default svg viewer or editor. #' #' This is useful primarily for testing or post-processing the SVG. #' #' @inheritParams htmlSVG #' @inheritParams svglite #' @keywords internal #' @export #' @examples #' if (interactive()) { #' editSVG(plot(1:10)) #' editSVG(contour(volcano)) #' } editSVG <- function(code, ..., width = NA, height = NA) { dim <- plot_dim(c(width, height)) tmp <- tempfile(fileext = ".svg") svglite(tmp, width = dim[1], height = dim[2], ...) tryCatch(code, finally = grDevices::dev.off() ) system(sprintf("open %s", shQuote(tmp))) } #' Run plotting code and return svg as string #' #' This is useful primarily for testing but can be used as an #' alternative to \code{\link{svgstring}()}. #' #' @inheritParams htmlSVG #' @keywords internal #' @export #' @examples #' stringSVG(plot(1:10)) stringSVG <- function(code, ...) { svg <- inlineSVG(code, ...) structure(svg, class = "svg") } inlineSVG <- function(code, ..., width = NA, height = NA) { dim <- plot_dim(c(width, height)) svg <- svgstring(width = dim[1], height = dim[2], ...) tryCatch(code, finally = grDevices::dev.off() ) out <- svg() class(out) <- NULL out } svglite/R/utils.R0000644000176200001440000000542514401573211013417 0ustar liggesusers mini_plot <- function(...) graphics::plot(..., axes = FALSE, xlab = "", ylab = "") plot_dim <- function(dim = c(NA, NA)) { if (any(is.na(dim))) { if (length(grDevices::dev.list()) == 0) { default_dim <- c(10, 8) } else { default_dim <- grDevices::dev.size() } dim[is.na(dim)] <- default_dim[is.na(dim)] dim_f <- prettyNum(dim, digits = 3) message("Saving ", dim_f[1], "\" x ", dim_f[2], "\" image") } dim } vapply_chr <- function(.x, .f, ...) { vapply(.x, .f, character(1), ...) } vapply_lgl <- function(.x, .f, ...) { vapply(.x, .f, logical(1), ...) } lapply_if <- function(.x, .p, .f, ...) { if (!is.logical(.p)) { .p <- vapply_lgl(.x, .p) } .x[.p] <- lapply(.x[.p], .f, ...) .x } keep <- function(.x, .p, ...) { .x[vapply_lgl(.x, .p, ...)] } compact <- function(x) { Filter(length, x) } `%||%` <- function(x, y) { if (is.null(x)) y else x } is_scalar_character <- function(x) { is.character(x) && length(x) == 1 } names2 <- function(x) { names(x) %||% rep("", length(x)) } ilapply <- function(.x, .f, ...) { idx <- names(.x) %||% seq_along(.x) out <- Map(.f, names(.x), .x, ...) names(out) <- names(.x) out } ilapply_if <- function(.x, .p, .f, ...) { if (!is.logical(.p)) { .p <- vapply_lgl(.x, .p) } .x[.p] <- ilapply(.x[.p], .f, ...) .x } set_names <- function(x, nm = x) { stats::setNames(x, nm) } zip <- function(.l) { fields <- set_names(names(.l[[1]])) lapply(fields, function(i) { lapply(.l, .subset2, i) }) } svglite_manual_tests <- new.env() register_manual_test <- function(file) { testthat_dir <- getwd() testfile <- file.path(testthat_dir, file) assign(file, testfile, svglite_manual_tests) } init_manual_tests <- function() { remove(list = names(svglite_manual_tests), envir = svglite_manual_tests) } open_manual_tests <- function() { lapply(names(svglite_manual_tests), function(test) { utils::browseURL(svglite_manual_tests[[test]]) }) } invalid_filename <- function(filename) { if (!is.character(filename) || length(filename) != 1) { return(TRUE) } # strip double occurences of % stripped_file <- gsub("%{2}", "", filename) # filename is fine if there are no % left if (!grepl("%", stripped_file)) { return(FALSE) } # remove first allowed pattern, % followed by digits followed by [diouxX] stripped_file <- sub("%[#0 ,+-]*[0-9.]*[diouxX]", "", stripped_file) # matching leftover % indicates multiple patterns or a single incorrect pattern (e.g., %s) return(grepl("%", stripped_file)) } #' Convert an svg file to svgz, overwriting the old file #' @param file the path to the file to convert #' @keywords internal #' @export create_svgz <- function(file) { svg <- readLines(file) out <- gzfile(file, "w") writeLines(svg, out) close(out) invisible(NULL) } svglite/R/aaa.R0000644000176200001440000000036314006252151012773 0ustar liggesusers#' @importFrom systemfonts register_variant #' @export systemfonts::register_variant #' @importFrom systemfonts register_font #' @export systemfonts::register_font #' @importFrom systemfonts font_feature #' @export systemfonts::font_feature svglite/R/fonts.R0000644000176200001440000001516414401573735013424 0ustar liggesusers r_font_families <- c("sans", "serif", "mono", "symbol") r_font_faces <- c("plain", "bold", "italic", "bolditalic", "symbol") alias_lookup <- function() { if (.Platform$OS.type == "windows") { serif_font <- "Times New Roman" symbol_font <- "Standard Symbols L" } else { serif_font <- "Times" symbol_font <- "Symbol" } c( sans = "Arial", serif = serif_font, mono = "Courier", symbol = symbol_font ) } #' @importFrom systemfonts font_info match_family <- function(font, bold = FALSE, italic = FALSE) { font_info(font, bold = bold, italic = italic)$family[1] } validate_aliases <- function(system_fonts, user_fonts) { system_fonts <- compact(lapply(system_fonts, compact)) user_fonts <- compact(lapply(user_fonts, compact)) system_fonts <- lapply(system_fonts, validate_system_alias) user_fonts <- ilapply(user_fonts, validate_user_alias) aliases <- c(names(system_fonts), names(user_fonts)) if (any(duplicated(aliases))) { stop("Cannot supply both system and font alias", call. = FALSE) } # Add missing system fonts for base families missing_aliases <- setdiff(r_font_families, aliases) system_fonts[missing_aliases] <- lapply(alias_lookup()[missing_aliases], match_family) list( system = system_fonts, user = user_fonts ) } validate_system_alias <- function(alias) { if (!is_scalar_character(alias)) { stop("System fonts must be scalar character vector", call. = FALSE) } matched <- match_family(alias) if (alias != matched) { warning( call. = FALSE, "System font `", alias, "` not found. ", "Closest match: `", matched, "`" ) } matched } is_user_alias <- function(x) { is.list(x) && (is_scalar_character(x$file) || is_scalar_character(x$ttf)) && (is_scalar_character(x$alias) || is_scalar_character(x$name)) } validate_user_alias <- function(default_name, family) { if (!all(names(family) %in% r_font_faces)) { stop("Faces must contain only: `plain`, `bold`, `italic`, `bolditalic`, `symbol`", call. = FALSE ) } is_alias_object <- vapply_lgl(family, is_user_alias) is_alias_plain <- vapply_lgl(family, is_scalar_character) is_valid_alias <- is_alias_object | is_alias_plain if (any(!is_valid_alias)) { stop( call. = FALSE, "The following faces are invalid for `", default_name, "`: ", paste0(names(family)[!is_valid_alias], collapse = ", ") ) } names <- ifelse(is_alias_plain, default_name, family) names <- lapply_if(names, is_alias_object, function(obj) { obj$alias %||% obj$name }) files <- lapply_if(family, is_alias_object, function(obj) { obj$file %||% obj$ttf }) file_exists <- vapply_lgl(files, file.exists) if (any(!file_exists)) { missing <- unlist(files)[!file_exists] stop( call. = FALSE, "Could not find font file: ", paste0(missing, collapse = ", ") ) } zip(list(name = names, file = files)) } #' Create a font-face specification #' #' Webfonts in SVG and HTML can either be specified manually using the #' `@font-face` at-rule, or imported from e.g. Google Fonts using the `@import` #' at-rule. `font_face()` helps you create a valid `@font-face` block for the #' `web_fonts` argument in [svglite()] and [svgstring()] functions. #' #' @param family The font family name this font should respond to. #' @param woff2,woff,ttf,otf,eot,svg URLs to the font in different formats. At #' least one must be given. Best browser support is provided by the woff #' format. #' @param local One or more font names that local installations of the font may #' have. If a local font is found with either of the given names it will be #' used and no download will happen. #' @param weight An optional value for the `font-weight` descriptor #' @param style An optional value for the `font-style` descriptor #' @param range An optional value for the `unicode-range` descriptor Will give #' the range of unicode values that this font will support #' @param variant An optional value for the `font-variant` descriptor #' @param stretch An optional value for the `font-stretch` descriptor #' @param feature_setting An optional value for the `font-feature-settings` #' descriptor It is recommended to avoid using this if possible #' @param variation_setting An optional value for the `font-variation-settings` #' descriptor. #' #' @return A character string with the `@font-face` block. #' #' @export #' @examples #' font_face( #' family = "MyHelvetica", #' ttf = "MgOpenModernaBold.ttf", #' local = c("Helvetica Neue Bold", "HelveticaNeue-Bold"), #' weight = "bold" #' ) #' font_face <- function(family, woff2 = NULL, woff = NULL, ttf = NULL, otf = NULL, eot = NULL, svg = NULL, local = NULL, weight = NULL, style = NULL, range = NULL, variant = NULL, stretch = NULL, feature_setting = NULL, variation_setting = NULL) { sources <- c( if (!is.null(local)) paste0('local("', local, '")'), if (!is.null(woff2)) paste0('url("', woff2, '") format("woff2")'), if (!is.null(woff)) paste0('url("', woff, '") format("woff")'), if (!is.null(otf)) paste0('url("', otf, '") format("opentype")'), if (!is.null(ttf)) paste0('url("', ttf, '") format("truetype")'), if (!is.null(eot)) paste0('url("', eot, '") format("embedded-opentype")'), if (!is.null(svg)) paste0('url("', svg, '") format("woff")') ) if (length(sources) == 0) { stop("At least one font source must be given") } x <- c( ' @font-face {\n', ' font-family: "', family, '";\n', ' src: ', paste0(paste(sources, collapse = ",\n "), ';\n'), if (!is.null(range)) paste0( ' unicode-range: ', range[1], ';\n'), if (!is.null(variant)) paste0( ' font-variant: ', variant[1], ';\n'), if (!is.null(feature_setting)) paste0( ' font-feature-settings: ', feature_setting[1], ';\n'), if (!is.null(variation_setting)) paste0( ' font-variation-settings: ', variation_setting[1], ';\n'), if (!is.null(stretch)) paste0( ' font-stretch: ', stretch[1], ';\n'), if (!is.null(weight)) paste0( ' font-weight: ', weight[1], ';\n'), if (!is.null(style)) paste0( ' font-style: ', style[1], ';\n'), ' }' ) x <- paste(x, collapse = "") class(x) <- c("font_face", "character") x } #' @export print.font_face <- function(x, ...) { cat(x) invisible(x) } is_font_face <- function(x) inherits(x, "font_face") validate_web_fonts <- function(x) { if (length(x) == 0) { return("") } paste0(paste( ifelse(vapply(x, is_font_face, logical(1)), x, paste0(' @import url("', x, '");')), collapse = "\n" ), "\n") } svglite/R/svglite-package.R0000644000176200001440000000017114511447263015327 0ustar liggesusers#' @keywords internal #' @aliases svglite-package "_PACKAGE" ## usethis namespace: start ## usethis namespace: end NULL svglite/R/cpp11.R0000644000176200001440000000117514006334503013201 0ustar liggesusers# Generated by cpp11: do not edit by hand svglite_ <- function(file, bg, width, height, pointsize, standalone, aliases, webfonts, id, fix_text_size, scaling, always_valid) { .Call(`_svglite_svglite_`, file, bg, width, height, pointsize, standalone, aliases, webfonts, id, fix_text_size, scaling, always_valid) } svgstring_ <- function(env, bg, width, height, pointsize, standalone, aliases, webfonts, id, fix_text_size, scaling) { .Call(`_svglite_svgstring_`, env, bg, width, height, pointsize, standalone, aliases, webfonts, id, fix_text_size, scaling) } get_svg_content <- function(p) { .Call(`_svglite_get_svg_content`, p) } svglite/R/SVG.R0000644000176200001440000001505314401573210012713 0ustar liggesusers#' An SVG Graphics Driver #' #' This function produces graphics compliant to the current w3 svg XML #' standard. The driver output is currently NOT specifying a DOCTYPE DTD. #' #' svglite provides two ways of controlling fonts: system fonts #' aliases and user fonts aliases. Supplying a font alias has two #' effects. First it determines the \code{font-family} property of all #' text anchors in the SVG output. Secondly, the font is used to #' determine the dimensions of graphical elements and has thus an #' influence on the overall aspect of the plots. This means that for #' optimal display, the font must be available on both the computer #' used to create the svg, and the computer used to render the #' svg. See the \code{fonts} vignette for more information. #' #' @param filename The file where output will appear. #' @param height,width Height and width in inches. #' @param bg Default background color for the plot (defaults to "white"). #' @param pointsize Default point size. #' @param standalone Produce a standalone svg file? If \code{FALSE}, omits #' xml header and default namespace. #' @param system_fonts `r lifecycle::badge('superseded')` *Consider using #' [systemfonts::register_font()] instead*. Named list of font #' names to be aliased with fonts installed on your system. If unspecified, #' the R default families \code{sans}, \code{serif}, \code{mono} and #' \code{symbol} are aliased to the family returned by #' \code{\link[systemfonts]{font_info}()}. #' @param user_fonts `r lifecycle::badge('superseded')` *Consider using #' [systemfonts::register_font()] instead*. Named list of fonts to #' be aliased with font files provided by the user rather than fonts properly #' installed on the system. The aliases can be fonts from the fontquiver #' package, strings containing a path to a font file, or a list containing #' \code{name} and \code{file} elements with \code{name} indicating #' the font alias in the SVG output and \code{file} the path to a #' font file. #' @param web_fonts A list containing web fonts to use in the SVG. The fonts #' will still need to be available locally on the computer running the code, #' but viewers of the final SVG will not need the font if specified as a web #' font. Web fonts can either be specified using [font_face()] or given as a #' single string in which case they are taken to be URL's for an `@import` #' directive to e.g. Google Fonts. #' @param id A character vector of ids to assign to the generated SVG's. If #' creating more SVG files than supplied ids the exceeding SVG's will not have #' an id tag and a warning will be thrown. #' @param fix_text_size Should the width of strings be fixed so that it doesn't #' change between svg renderers depending on their font rendering? Defaults to #' `TRUE`. If `TRUE` each string will have the `textLength` CSS property set #' to the width calculated by systemfonts and #' `lengthAdjust='spacingAndGlyphs'`. Setting this to `FALSE` can be #' beneficial for heavy post-processing that may change content or style of #' strings, but may lead to inconsistencies between strings and graphic #' elements that depend on the dimensions of the string (e.g. label borders #' and background). #' @param scaling A scaling factor to apply to the rendered line width and text #' size. Useful for getting the right sizing at the dimension that you #' need. #' @param always_valid Should the svgfile be a valid svg file while it is being #' written to? Setting this to `TRUE` will incur a considerable performance #' hit (>50% additional rendering time) so this should only be set to `TRUE` #' if the file is being parsed while it is still being written to. #' @param file Identical to `filename`. Provided for backward compatibility. #' @references \emph{W3C Scalable Vector Graphics (SVG)}: #' \url{https://www.w3.org/Graphics/SVG/} #' @author This driver was written by T Jake Luciani #' \email{jakeluciani@@yahoo.com} 2012: updated by Matthieu Decorde #' \email{matthieu.decorde@@ens-lyon.fr} #' @seealso \code{\link{pictex}}, \code{\link{postscript}}, \code{\link{Devices}} #' #' @examples #' # Save to file #' svglite(tempfile("Rplots.svg")) #' plot(1:11, (-5:5)^2, type = "b", main = "Simple Example") #' dev.off() #' #' @keywords device #' @useDynLib svglite, .registration = TRUE #' @importFrom systemfonts match_font #' @export svglite <- function(filename = "Rplot%03d.svg", width = 10, height = 8, bg = "white", pointsize = 12, standalone = TRUE, system_fonts = list(), user_fonts = list(), web_fonts = list(), id = NULL, fix_text_size = TRUE, scaling = 1, always_valid = FALSE, file) { if (!missing(file)) { filename <- file } if (invalid_filename(filename)) { stop("invalid 'file': ", filename) } aliases <- validate_aliases(system_fonts, user_fonts) web_fonts <- validate_web_fonts(web_fonts) if (is.null(id)) { id <- character(0) } id <- as.character(id) invisible(svglite_(filename, bg, width, height, pointsize, standalone, aliases, web_fonts, id, fix_text_size, scaling, always_valid)) } #' Access current SVG as a string. #' #' This is a variation on \code{\link{svglite}} that makes it easy to access #' the current value as a string. #' #' See \code{\link{svglite}()} documentation for information about #' specifying fonts. #' #' @return A function with no arguments: call the function to get the #' current value of the string. #' @examples #' s <- svgstring() #' s() #' #' plot.new() #' s() #' text(0.5, 0.5, "Hi!") #' s() #' dev.off() #' #' s <- svgstring() #' plot(rnorm(5), rnorm(5)) #' s() #' dev.off() #' @inheritParams svglite #' @export svgstring <- function(width = 10, height = 8, bg = "white", pointsize = 12, standalone = TRUE, system_fonts = list(), user_fonts = list(), web_fonts = list(), id = NULL, fix_text_size = TRUE, scaling = 1) { aliases <- validate_aliases(system_fonts, user_fonts) web_fonts <- validate_web_fonts(web_fonts) if (is.null(id)) { id <- character(0) } id <- as.character(id) env <- new.env(parent = emptyenv()) string_src <- svgstring_(env, width = width, height = height, bg = bg, pointsize = pointsize, standalone = standalone, aliases = aliases, webfonts = web_fonts, id = id, fix_text_size, scaling ) function() { svgstr <- env$svg_string if (!env$is_closed) { svgstr <- c(svgstr, get_svg_content(string_src)) } structure(svgstr, class = "svg") } } #' @export print.svg <- function(x, ...) cat(x, sep = "\n") svglite/NEWS.md0000644000176200001440000001336714534626546013056 0ustar liggesusers# svglite 2.1.3 * Fix a stack imbalance bug # svglite 2.1.2 * Windows: use libpng included with Rtools on R 4.2 and up. * Add support for `dev.capabilities()` # svglite 2.1.1 * Fix `` include at request of CRAN # svglite 2.1.0 * Add new graphics engine features: - Path clipping - Masking - Pattern and gradient fills * Font family names are now quoted (#136) * svglite now renders leading white-space in nodes. (#131, @hrbrmstr) # svglite 2.0.0 * svglite now uses systemfonts for text metric calculations and font family lookup. * svglite can now encode OpenType font features into the CSS if the used font contains registered features * svglite now directly encodes raster data into png instead of rendering it through cairo. If the provided raster is smaller than the final requested size it will be resized. * SVG's can now get a top-level id so that style definitions doesn't spill into the surrounding HTML (#91) * Dimensions are now encoded into the top-level `` tag (#90) * Starting a new page creates a new file if the filename supports it (#98, @vandenman). * The _inline_ devices now defaults to the same dimensions as `svglite()` (#89) * Clip defs are now only written if they don't already exist (#110) * Clipping is now defined with outer groups instead of on each element (#109) * svglite now uses cpp11 instead of Rcpp (#100) * svgz output is now supported natively (#6) * Text adjustments are now encoded in css where possible (#107) * The use of textLength CSS property can now be turned off (#118) * web font imports can now be given when creating an svg (#108) * Add scaling argument to devices to control line-width and text scaling (#115) * svg files that are being written are now only valid at all times if `always_valid` is set to `TRUE` in the `svglite()` call. * svglite now guards against attempts at writing to the device before a new page has been requested (#126) # svglite 1.2.3 * The radius of circles is no longer expressed in pt (#93, @vandenman). * Dimensions smaller than 1 now retain two significant figures (#94, @ilia-kats). * @thomasp85 takes over as maintainer # svglite 1.2.2 * Improvements to reproducibility of generated SVGs: Negative zeros are now treated as positive, and the clip ID are now generated from truncated elements. * svglite now uses the `polygon` SVG element. This ensures that polygons are properly closed (#82). * Text metrics are now correctly computed for Unicode characters in plotmath expressions (#81). # svglite 1.2.1 This release makes svglite compatible with gdtools 0.1.6 # svglite 1.2.0 ## New features * The device functions gain `system_fonts` and `user_fonts` arguments. * Two new vignettes: `vignette("fonts")` and `vignette("scaling")`. The vignette on fonts explains in detail how to use the new fonts arguments and why. The vignette on scaling goes over scaling issues, e.g. when embedding svglite outputs in a web page. * `xmlSVG()` gains `height` and `width` arguments (#66). * New `stringSVG()` device function for quick testing. ## Improvements * Greatly improves the performance of `svgstring()` (#58). * Clip paths now get a unique identifier to avoid collisions when multiple plots are included in a document (#67). * Raster graphics are now correctly cropped (#64) and handle transparency properly. * The dimensions of text fields are now hardcoded in the SVGs to prevent alignment issues. ## Bug fixes * `editSVG()` works again (#56). * The dashes in lines with `lwd < 1` are scaled better (#68). * Transparent blacks are written correctly (#62, #63). * Text is now scaled correctly (#72, #59). See also the new vignette on scaling. # svglite 1.1.0 * Text metrics now converted from points to pixels (#45, #48) - this fixes text alignment issues. * Intermediate outputs are always valid SVG (#53). * New `svgstring()` returns plot as a string (#40, @yixuan). * Use raster test compatible with older versions of R. * Add support for `clip()`. This also fixes a number of minor issues with grid graphics (@yixuan, #47 and #49). * Fix incorrect device size (#50). # svglite 1.0.0 svglite is fully featured svg graphics device that works on all platforms, forked from RSvgDevice. It supports all graphics device features: * All types of line dashing are supported (#15). All line end and line join styles are supported (#24). * Text is now coloured, and uses the same default fonts as R (Arial, Times New Roman, and Courier). Font metrics are computed using the gdtools package so that `plotmath()` and `strwidth()` now work. * Transparent colours are now generated with `rgba()` rather than using `stroke-opacity` and `fill-opacity` styles (#16). NA fills and colours are translated to "none". * `par(bg)` affects the background colour (#8). * Rasters are supported by embedding base64-encoded pngs in a data url (#2). * `polypath()` is now supported, which also allows the `showtext` package to render fonts correctly with this device (#36). We also made a few other tweaks to the rendered SVG: * Only the `viewBox` attribute of `` is set (not `width` and `height`): I'm reasonably certain this makes it easier to use in more places (#12). * Default styling properties are specified in a global `